hyperframes 0.8.186 → 0.8.189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/{add-UGK7AZI6.js → add-TIKUM2YX.js} +12 -12
  2. package/dist/{animatedGifPrep-HPZPSB43.js → animatedGifPrep-25X4G5IE.js} +7 -7
  3. package/dist/api.js +15 -15
  4. package/dist/{asset-X2GTFWOY.js → asset-TPKLIP4R.js} +2 -2
  5. package/dist/{assetCataloger-5NWEG4IU.js → assetCataloger-PQAIV3FI.js} +7 -7
  6. package/dist/{audio-7PBM5F3H.js → audio-6RLPKB5X.js} +15 -15
  7. package/dist/{auth-WABSMO46.js → auth-O7QSTP6Y.js} +1 -1
  8. package/dist/{autoUpdate-6SCMLTSC.js → autoUpdate-TIEJODP7.js} +2 -2
  9. package/dist/{backgroundChecks-XOGPAAR3.js → backgroundChecks-K2AXYDYG.js} +3 -3
  10. package/dist/backgroundChecksWorker.js +3 -3
  11. package/dist/{batchRender-VR6IA77K.js → batchRender-ORVWYJAH.js} +4 -4
  12. package/dist/{beats-3IWXLMSC.js → beats-2VTB2ATR.js} +4 -4
  13. package/dist/{benchmark-2SSYATCX.js → benchmark-NLNBL6YR.js} +9 -9
  14. package/dist/{browser-PMRN345D.js → browser-M5UV6VJB.js} +3 -3
  15. package/dist/{bundleWithLocalizedFonts-TT32MZBO.js → bundleWithLocalizedFonts-EDD2XDS2.js} +2 -2
  16. package/dist/{capture-4VD6AKUJ.js → capture-JSYVNFKO.js} +10 -10
  17. package/dist/{capture-BDGYTE57.js → capture-WHWEE5FE.js} +2 -2
  18. package/dist/{captureCompositionFrame-5FVLAETA.js → captureCompositionFrame-JANGQCIA.js} +13 -13
  19. package/dist/{catalog-HUYZO5D7.js → catalog-MPOOKNSZ.js} +12 -12
  20. package/dist/{catch-up-SSGTR2ZT.js → catch-up-6KZV3UMZ.js} +4 -4
  21. package/dist/{check-JWUWHGM2.js → check-G273NWK7.js} +37 -36
  22. package/dist/{checkBrowser-X7G6YNR7.js → checkBrowser-G53E2ZQI.js} +32 -33
  23. package/dist/{chunk-WHMFW5ET.js → chunk-2CYM34M7.js} +3 -3
  24. package/dist/{chunk-HZ7M2MZA.js → chunk-37EPEFXM.js} +1 -1
  25. package/dist/{chunk-4XFFJAWS.js → chunk-3OLOVRWS.js} +1 -1
  26. package/dist/{chunk-ZIRIDSDB.js → chunk-3X6LH25W.js} +1 -1
  27. package/dist/{chunk-TH7J3JZ5.js → chunk-44TJOEH5.js} +92 -25
  28. package/dist/{chunk-K6KBOORN.js → chunk-4DZ3HBKF.js} +2 -2
  29. package/dist/{chunk-U7QTMRLW.js → chunk-4QFKGRTB.js} +2 -2
  30. package/dist/{chunk-7MVODWQF.js → chunk-5544QB3U.js} +1 -1
  31. package/dist/{chunk-7AUMTARQ.js → chunk-565X6Q4X.js} +3 -3
  32. package/dist/{chunk-J36JVBLR.js → chunk-5C7R55BJ.js} +1 -1
  33. package/dist/{chunk-YRAJDFIN.js → chunk-5HJJEHGE.js} +1 -1
  34. package/dist/{chunk-5UBZPG74.js → chunk-6A45RKAK.js} +1 -1
  35. package/dist/{chunk-ROWEDRVX.js → chunk-6RA4TWGV.js} +1 -1
  36. package/dist/{chunk-3YHGQJI6.js → chunk-74Y43RJZ.js} +3 -3
  37. package/dist/{chunk-4AFNEKU7.js → chunk-ANYBRVPF.js} +1 -1
  38. package/dist/{chunk-R6IGQ5NO.js → chunk-AQ3L7DVB.js} +2 -2
  39. package/dist/{chunk-LDW3KVGK.js → chunk-ASZ4IQB4.js} +2 -2
  40. package/dist/{chunk-DQZMJS2P.js → chunk-AYBKDHYD.js} +2 -2
  41. package/dist/{chunk-RVZNSKRH.js → chunk-B52BJM5K.js} +2 -2
  42. package/dist/{chunk-GTMBII7X.js → chunk-B634ZCY2.js} +1 -1
  43. package/dist/chunk-BPX7LZG6.js +3896 -0
  44. package/dist/{chunk-5ZMIVTHN.js → chunk-BZQLTNMB.js} +219 -79
  45. package/dist/{chunk-MY6CVBDD.js → chunk-CE7EQUBV.js} +1 -1
  46. package/dist/{chunk-ZVBEGBII.js → chunk-CSYSEPWL.js} +31 -7
  47. package/dist/{chunk-EZINX6HH.js → chunk-CTF7BYCV.js} +3 -3
  48. package/dist/{chunk-2II34JPM.js → chunk-E676Y7AY.js} +82 -29
  49. package/dist/{chunk-YQICCAVA.js → chunk-E7TI5PAQ.js} +1 -1
  50. package/dist/{chunk-TTVHYD5R.js → chunk-EI7FP7F3.js} +1 -1
  51. package/dist/{chunk-UJZT6HNN.js → chunk-F45HHA3M.js} +1 -1
  52. package/dist/{chunk-TQ2RRB5V.js → chunk-FJF5JBYU.js} +1 -1
  53. package/dist/{chunk-Z5233OAN.js → chunk-GBT7P3OQ.js} +5 -5
  54. package/dist/{chunk-NPDPDEPP.js → chunk-GWYMAII7.js} +1 -1
  55. package/dist/{chunk-UAJCTEK2.js → chunk-HNMDLKA4.js} +1 -1
  56. package/dist/{chunk-FCWFV4P7.js → chunk-HQIFMCQN.js} +2278 -2273
  57. package/dist/{chunk-DTXDQ6OP.js → chunk-HUG47UEE.js} +1 -1
  58. package/dist/{chunk-7M7TMM4S.js → chunk-HXM2ISEK.js} +264 -124
  59. package/dist/{chunk-WLHNIIXU.js → chunk-IFXOMIBP.js} +3 -3
  60. package/dist/{chunk-77QM6YRQ.js → chunk-IPEP346Y.js} +1 -1
  61. package/dist/{chunk-22QKQDEE.js → chunk-JIPUWKLA.js} +14 -14
  62. package/dist/{chunk-YNH6ERDL.js → chunk-JZDVIDFH.js} +2 -2
  63. package/dist/{chunk-KNLSYCI5.js → chunk-KLWUB37U.js} +7 -7
  64. package/dist/{chunk-PEIV2SOG.js → chunk-KS4HMJJD.js} +1 -1
  65. package/dist/{chunk-4NTW45W5.js → chunk-M6FHSQZ3.js} +3 -3
  66. package/dist/{chunk-RFJ6SX4U.js → chunk-NDBWI7OA.js} +5 -5
  67. package/dist/{chunk-ACLABKEF.js → chunk-NSJ5HD6A.js} +5 -5
  68. package/dist/{chunk-FRLTKTOR.js → chunk-OHXHBMF4.js} +1 -1
  69. package/dist/{chunk-7MLFYMFB.js → chunk-QMFI4N7J.js} +1 -1
  70. package/dist/{chunk-HOFX63F2.js → chunk-QYQ2EYOI.js} +3 -3
  71. package/dist/{chunk-27H2363S.js → chunk-R5FS7GFU.js} +1 -1
  72. package/dist/{chunk-PFD25NAC.js → chunk-RIJHDTM5.js} +4 -0
  73. package/dist/{chunk-FH7WODJH.js → chunk-SGFMCNLY.js} +8 -0
  74. package/dist/{chunk-BRTDLVK3.js → chunk-SMF6YAUL.js} +9 -11
  75. package/dist/{chunk-6IOBAOU7.js → chunk-T37XXYRK.js} +1 -1
  76. package/dist/{chunk-T4RP55P2.js → chunk-TFUAKV6O.js} +1 -1
  77. package/dist/{chunk-U6RXZNZE.js → chunk-TMBUBLZB.js} +55 -23
  78. package/dist/{chunk-5ZMXGNGC.js → chunk-TPTQHOXU.js} +9 -9
  79. package/dist/{chunk-U4BMRIE6.js → chunk-TREMHDWT.js} +12 -10
  80. package/dist/{chunk-Q447CX26.js → chunk-TSFA2Y5C.js} +60 -47
  81. package/dist/{chunk-Y2M6XSNK.js → chunk-VRQU564T.js} +1 -1
  82. package/dist/{chunk-ID5AJ4L3.js → chunk-WMJJCHVQ.js} +2 -2
  83. package/dist/{chunk-RNZEQTWZ.js → chunk-WYCFMNU3.js} +15 -2
  84. package/dist/{chunk-UJKQWGFF.js → chunk-X7HBSTCZ.js} +8 -0
  85. package/dist/{chunk-ZKBW7NU2.js → chunk-XJVXROOL.js} +2 -2
  86. package/dist/{chunk-CML25PAF.js → chunk-XNFDDVJS.js} +27 -3
  87. package/dist/{chunk-RPUIMFHF.js → chunk-Z2BW2WJS.js} +1 -1
  88. package/dist/{chunk-2PZZMAME.js → chunk-ZPMF6GAQ.js} +17 -12
  89. package/dist/{clean-IM2JIAUT.js → clean-PS4VYGQ6.js} +17 -17
  90. package/dist/cli.js +53 -53
  91. package/dist/{cloud-6IPBCJSX.js → cloud-E47CE5VQ.js} +4 -4
  92. package/dist/{cloudrun-MWZ6S2I2.js → cloudrun-3J4SQBXE.js} +5 -5
  93. package/dist/{compare-WD7A2TV5.js → compare-EAKHXBDO.js} +29 -29
  94. package/dist/{compiler-2EKSMC3I.js → compiler-23MT6CX6.js} +4 -4
  95. package/dist/{component-JKALWH36.js → component-QBOXYJJS.js} +3 -3
  96. package/dist/{compositions-643NAG4A.js → compositions-QC3EO3MJ.js} +11 -11
  97. package/dist/{delete-EASPXKHQ.js → delete-TXEOLPC6.js} +4 -4
  98. package/dist/deploy-3B3WJ6PM.js +300 -0
  99. package/dist/{destroy-4B5HW5FH.js → destroy-SSPAKADI.js} +13 -3
  100. package/dist/{dist-F6FOKRSU.js → dist-F4UJFK4S.js} +5 -3
  101. package/dist/{dist-LM7AVHOU.js → dist-JH32CBQL.js} +1 -1
  102. package/dist/{doctor-7XUVYDJQ.js → doctor-2CRFH4B3.js} +11 -11
  103. package/dist/{events-TULV6D3G.js → events-GUIOSBIB.js} +3 -3
  104. package/dist/{events-5TTPYBKG.js → events-KVTX3QFV.js} +2 -2
  105. package/dist/{feedback-4FTPWM6P.js → feedback-VQIOSTIB.js} +7 -7
  106. package/dist/{ffmpeg-MP7ZVYLZ.js → ffmpeg-BPCZGXFW.js} +7 -7
  107. package/dist/{figma-B7LFWYKO.js → figma-QIOKCH26.js} +3 -3
  108. package/dist/fontLocalizeCli.js +9 -9
  109. package/dist/{get-F7W4AQ2V.js → get-TU45SNXF.js} +4 -4
  110. package/dist/{gpuPolicy-NSNVDFXC.js → gpuPolicy-AWAJBGYY.js} +7 -7
  111. package/dist/{grade-compare-6QXDTLS2.js → grade-compare-LDNUXX3Q.js} +30 -30
  112. package/dist/{help-RJAA4O74.js → help-YUCHJKKL.js} +1 -1
  113. package/dist/{history-OOQBDVV5.js → history-MMHFUMIG.js} +7 -7
  114. package/dist/hyperframe.manifest.json +1 -1
  115. package/dist/hyperframe.runtime.iife.js +65 -64
  116. package/dist/hyperframes-player.global.js +3 -3
  117. package/dist/{info-5OGGYYLO.js → info-HBHOFSC2.js} +8 -8
  118. package/dist/{init-DM3TJKW2.js → init-3E4GUXIZ.js} +27 -27
  119. package/dist/{inspect-FECQCLS5.js → inspect-YNUPJVIH.js} +30 -30
  120. package/dist/{installer-WOG4VORV.js → installer-WLCRP33K.js} +5 -5
  121. package/dist/{keyframes-SZHWUF5Y.js → keyframes-Z47GHCGO.js} +6 -6
  122. package/dist/{lambda-JG2ZRGMC.js → lambda-CA4PEUO2.js} +20 -13
  123. package/dist/{layout-7ACF7ZKC.js → layout-YPV6PIKF.js} +30 -30
  124. package/dist/{lint-ZCQGMILV.js → lint-3AVVQ74A.js} +7 -7
  125. package/dist/{lintProject-JATQKR64.js → lintProject-XM3DLUKG.js} +1 -1
  126. package/dist/{list-MH2X3Y2T.js → list-JH52Y735.js} +7 -7
  127. package/dist/{login-CK46WX5U.js → login-G7NPJUAW.js} +4 -4
  128. package/dist/{manager-GXTPNJVW.js → manager-2LSMMZY4.js} +8 -8
  129. package/dist/{media-treatment-EH2AT34Z.js → media-treatment-BVMMD5EN.js} +13 -13
  130. package/dist/{models-D5XP2UK6.js → models-P3TNRVRV.js} +11 -11
  131. package/dist/{motionShot-UR7D3SRQ.js → motionShot-LK62QHNE.js} +18 -18
  132. package/dist/{normalize-audio-635JOR5W.js → normalize-audio-Y7BAQ7AR.js} +6 -6
  133. package/dist/{open-6WE5WFVM.js → open-CCFFLAT7.js} +12 -12
  134. package/dist/{parakeetRunner-STHZPG32.js → parakeetRunner-N2BWSOSD.js} +12 -12
  135. package/dist/{pipeline-LVEKRETU.js → pipeline-3VT2DQGU.js} +8 -8
  136. package/dist/{play-W6TDW4FE.js → play-G4I5IFHO.js} +20 -20
  137. package/dist/{policies-XXNGXWHV.js → policies-MBWGLMWB.js} +17 -0
  138. package/dist/{preflight-LIMQXLR7.js → preflight-2KU2QLCZ.js} +8 -8
  139. package/dist/{present-RX5QX6UR.js → present-EJ7XHGQO.js} +16 -16
  140. package/dist/{preview-KIWTWK63.js → preview-O23BYFAY.js} +18 -18
  141. package/dist/{producer-TSNKSCUM.js → producer-UJQVLKLP.js} +1 -1
  142. package/dist/{progress-NHJIHAAG.js → progress-PX3PVAIM.js} +5 -3
  143. package/dist/{projectConfig-NCP7JRH4.js → projectConfig-QFVIBT37.js} +7 -7
  144. package/dist/{publication-5W5UZWGC.js → publication-4UYGFB2X.js} +3 -3
  145. package/dist/{publish-7WR4XJ6I.js → publish-MHLVJLCR.js} +9 -9
  146. package/dist/{remove-background-43JGBRWQ.js → remove-background-N7AHTZWP.js} +9 -9
  147. package/dist/{render-7T44HIM6.js → render-6HQV4LZJ.js} +11 -9
  148. package/dist/{render-WHMLYIOI.js → render-CDATEVDJ.js} +31 -22
  149. package/dist/{render-456HCO6O.js → render-EI2ODKOP.js} +45 -29
  150. package/dist/{render-batch-LURBJTBV.js → render-batch-F7HMC7XH.js} +7 -7
  151. package/dist/renderSetupWorker.js +1 -1
  152. package/dist/{resolver-MBIW4VC4.js → resolver-63DB5JCY.js} +5 -5
  153. package/dist/{sam-MC24SZHC.js → sam-FHJ7IL7T.js} +3 -1
  154. package/dist/{sherpa-5J2AALED.js → sherpa-AK3MTPL3.js} +11 -11
  155. package/dist/{sites-MAVH57MO.js → sites-5PWHAMEG.js} +1 -1
  156. package/dist/skills/hyperframes/references/motion.md +1 -0
  157. package/dist/{skills-UHOXOGGF.js → skills-EVIDL7PV.js} +5 -5
  158. package/dist/{skillsUpdateCheck-PDZIFUJE.js → skillsUpdateCheck-YCNJYDJO.js} +3 -3
  159. package/dist/{snapshot-PFV7ALLH.js → snapshot-WSYZKR3C.js} +35 -35
  160. package/dist/{src-XK5YIR6L.js → src-KTD6WTEV.js} +8 -8
  161. package/dist/{src-DCVSEODX.js → src-YNXBU4ZQ.js} +12 -6
  162. package/dist/{state-ORRPOLMQ.js → state-JPNCCXAV.js} +3 -1
  163. package/dist/{staticProjectServer-4IE4G4JO.js → staticProjectServer-D2D3BKBP.js} +18 -16
  164. package/dist/studio/assets/{hyperframes-player-B_skzvYC.js → hyperframes-player-CkK8Zijf.js} +1 -1
  165. package/dist/studio/assets/{index-CgLbvCUo.js → index-ATnRoBug.js} +1 -1
  166. package/dist/studio/assets/{index-Dn5FAk2e.js → index-B2JcCaRF.js} +130 -130
  167. package/dist/studio/index.html +1 -1
  168. package/dist/{studio-api-JMWYU7KF.js → studio-api-3ZGQPE5J.js} +10 -10
  169. package/dist/{studioServer-BWLSTGIG.js → studioServer-VWM4J7ZP.js} +48 -48
  170. package/dist/{telemetry-JCU3AV7W.js → telemetry-BEVI64BB.js} +3 -3
  171. package/dist/templates/_shared/AGENTS.md +5 -0
  172. package/dist/templates/_shared/CLAUDE.md +5 -0
  173. package/dist/{timeline-BCIFQYOM.js → timeline-P57FUXJQ.js} +18 -18
  174. package/dist/{tokens-7IXKALMQ.js → tokens-OUQY6LPD.js} +2 -2
  175. package/dist/{transcribe-ZUQD3B32.js → transcribe-CK66VIU4.js} +10 -10
  176. package/dist/{transcribe-HW7HGJ4V.js → transcribe-QUPX67KC.js} +16 -16
  177. package/dist/{updateCheck-Z6LNEK7B.js → updateCheck-XZ43CH3L.js} +2 -2
  178. package/dist/{upgrade-JYH72JGL.js → upgrade-7QQNGI7I.js} +2 -2
  179. package/dist/{urlDownloader-WKLFHSUH.js → urlDownloader-ESAEL6YX.js} +6 -6
  180. package/dist/{validate-QCC27YYI.js → validate-5GZ6KFRU.js} +24 -24
  181. package/dist/{video-EZ7ONY6L.js → video-AQNVR6PE.js} +7 -7
  182. package/dist/warmCheck.js +37 -38
  183. package/package.json +18 -2
  184. package/dist/chunk-KANNRHY7.js +0 -53
  185. package/dist/chunk-UOZX5W5T.js +0 -3852
  186. package/dist/deploy-CDKCQ5MX.js +0 -196
@@ -26,7 +26,7 @@ import {
26
26
  renderFrameIdForRenderId,
27
27
  resolveAuthoredTimingWindow,
28
28
  toFps
29
- } from "./chunk-RNZEQTWZ.js";
29
+ } from "./chunk-WYCFMNU3.js";
30
30
  import {
31
31
  AUDIO_GROUP_RENDER_ID_ATTR,
32
32
  COLOR_GRADING_SOURCE_HIDDEN_ATTR,
@@ -43,7 +43,10 @@ import {
43
43
  acceleratedCanvasTrackerSource,
44
44
  enabledAudioFxNodes,
45
45
  fxAutomationTarget,
46
+ gainedVolume,
47
+ gainedVolumePoints,
46
48
  injectScriptsIntoHtml,
49
+ isBentSegment,
47
50
  normalizeAudioFxParams,
48
51
  normalizePlaybackRate,
49
52
  normalizeRateSpec,
@@ -58,12 +61,14 @@ import {
58
61
  serializeAutomation,
59
62
  shiftRateLane,
60
63
  sourceTimeAt,
61
- timeAtSourceTime
62
- } from "./chunk-UOZX5W5T.js";
64
+ timeAtSourceTime,
65
+ volumeSegmentSampleCount
66
+ } from "./chunk-BPX7LZG6.js";
63
67
  import {
64
68
  clampAudioGain,
69
+ clipGain,
65
70
  isAudibleVideoElement
66
- } from "./chunk-UJKQWGFF.js";
71
+ } from "./chunk-X7HBSTCZ.js";
67
72
  import {
68
73
  redactTelemetryString,
69
74
  redactUrlQueryStrings
@@ -78,7 +83,7 @@ import {
78
83
  } from "./chunk-4UMPIPCQ.js";
79
84
  import {
80
85
  resolveReferencedStart
81
- } from "./chunk-Q447CX26.js";
86
+ } from "./chunk-TSFA2Y5C.js";
82
87
  import {
83
88
  FFMPEG_PATH_ENV,
84
89
  FFPROBE_PATH_ENV,
@@ -2991,6 +2996,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
2991
2996
  async (items, visualProperties, colorGradingSourceHiddenAttr) => {
2992
2997
  const injectedIds = [];
2993
2998
  const pendingDecodes = [];
2999
+ const imagesToPublish = [];
2994
3000
  const replacementLayoutProperties = /* @__PURE__ */ new Set([
2995
3001
  "width",
2996
3002
  "height",
@@ -3062,6 +3068,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
3062
3068
  img.style.objectFit = computedStyle.objectFit;
3063
3069
  img.style.objectPosition = computedStyle.objectPosition;
3064
3070
  img.style.zIndex = computedStyle.zIndex;
3071
+ img.style.setProperty("visibility", "hidden", "important");
3065
3072
  img.decoding = "sync";
3066
3073
  if (img.getAttribute("src") !== item.dataUri) {
3067
3074
  if (item.crossOrigin) img.crossOrigin = item.crossOrigin;
@@ -3077,7 +3084,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
3077
3084
  );
3078
3085
  }
3079
3086
  img.style.opacity = String(computedOpacity);
3080
- img.style.visibility = "visible";
3087
+ imagesToPublish.push(img);
3081
3088
  video.style.setProperty("visibility", "hidden", "important");
3082
3089
  video.style.setProperty("pointer-events", "none", "important");
3083
3090
  injectedIds.push(item.videoId);
@@ -3085,6 +3092,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
3085
3092
  if (pendingDecodes.length > 0) {
3086
3093
  await Promise.all(pendingDecodes);
3087
3094
  }
3095
+ for (const img of imagesToPublish) img.style.visibility = "visible";
3088
3096
  if (injectedIds.length > 0) {
3089
3097
  const redraw = window.__hf?.colorGrading?.redraw;
3090
3098
  redraw?.();
@@ -3123,7 +3131,7 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
3123
3131
  const img = video.nextElementSibling;
3124
3132
  const hasImg = img && img.classList.contains("__render_frame__");
3125
3133
  const ancestorHidden = isVisualAncestorHidden(video);
3126
- const visible = active.has(video.id) && !ancestorHidden;
3134
+ const visible = active.has(window.__hfMediaId?.(video) || video.id) && !ancestorHidden;
3127
3135
  if (visible) {
3128
3136
  video.style.setProperty("visibility", "hidden", "important");
3129
3137
  video.style.setProperty("pointer-events", "none", "important");
@@ -7634,6 +7642,57 @@ function getCapturePerfSummary(session) {
7634
7642
  };
7635
7643
  }
7636
7644
 
7645
+ // ../engine/src/services/extractedFrameIndex.ts
7646
+ import { readdirSync as readdirSync2 } from "fs";
7647
+ import { join as join4 } from "path";
7648
+ var FRAME_FILENAME_PREFIX = "frame_";
7649
+ function frameSequencePath(dir, framePattern) {
7650
+ return join4(dir.replaceAll("%", "%%"), framePattern);
7651
+ }
7652
+ var ExtractedFrameSequenceError = class extends Error {
7653
+ constructor(message) {
7654
+ super(message);
7655
+ this.name = "ExtractedFrameSequenceError";
7656
+ }
7657
+ };
7658
+ function extractedFrameIndex(file, format) {
7659
+ const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
7660
+ if (!match) {
7661
+ throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
7662
+ }
7663
+ const ordinal = Number.parseInt(match[1], 10);
7664
+ if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
7665
+ throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
7666
+ }
7667
+ return ordinal - 1;
7668
+ }
7669
+ function framePathsFromDirectory(outputDir, format, allowGaps = false) {
7670
+ const suffix = `.${format}`;
7671
+ const indexed = /* @__PURE__ */ new Map();
7672
+ for (const file of readdirSync2(outputDir)) {
7673
+ if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
7674
+ const index = extractedFrameIndex(file, format);
7675
+ if (indexed.has(index)) {
7676
+ throw new ExtractedFrameSequenceError(
7677
+ `Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
7678
+ );
7679
+ }
7680
+ indexed.set(index, join4(outputDir, file));
7681
+ }
7682
+ if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
7683
+ const ordered = /* @__PURE__ */ new Map();
7684
+ for (let index = 0; index < indexed.size; index += 1) {
7685
+ const path = indexed.get(index);
7686
+ if (!path) {
7687
+ throw new ExtractedFrameSequenceError(
7688
+ `Missing extracted frame index ${index} in ${outputDir}`
7689
+ );
7690
+ }
7691
+ ordered.set(index, path);
7692
+ }
7693
+ return ordered;
7694
+ }
7695
+
7637
7696
  // ../engine/src/services/chunkEncoder.ts
7638
7697
  import {
7639
7698
  closeSync,
@@ -7644,12 +7703,12 @@ import {
7644
7703
  mkdtempSync,
7645
7704
  openSync,
7646
7705
  readFileSync as readFileSync3,
7647
- readdirSync as readdirSync2,
7706
+ readdirSync as readdirSync3,
7648
7707
  statSync as statSync3,
7649
7708
  writeFileSync as writeFileSync2,
7650
7709
  writeSync
7651
7710
  } from "fs";
7652
- import { join as join5, dirname as dirname2, extname } from "path";
7711
+ import { join as join6, dirname as dirname2, extname } from "path";
7653
7712
 
7654
7713
  // ../engine/src/utils/gpuEncoder.ts
7655
7714
  import { spawn } from "child_process";
@@ -8934,9 +8993,15 @@ async function analyzeKeyframeIntervalsUncached(filePath, videoStreamStartSecond
8934
8993
  };
8935
8994
  }
8936
8995
 
8996
+ // ../engine/src/services/softwareEncoderThreads.ts
8997
+ import { availableParallelism } from "os";
8998
+ function appendSoftwareEncoderThreadsArg(args, parallelism = availableParallelism()) {
8999
+ args.push("-threads", String(Math.max(1, Math.floor(parallelism))));
9000
+ }
9001
+
8937
9002
  // ../engine/src/utils/renderProvenance.ts
8938
9003
  import { readFileSync as readFileSync2 } from "fs";
8939
- import { dirname, join as join4 } from "path";
9004
+ import { dirname, join as join5 } from "path";
8940
9005
  import { fileURLToPath } from "url";
8941
9006
  var PROVENANCE_RENDERER_TAG = "hyperframes_renderer";
8942
9007
  var PROVENANCE_VERSION_TAG = "hyperframes_version";
@@ -8947,7 +9012,7 @@ var MAX_WALK_DEPTH = 12;
8947
9012
  function resolveOwnVersionFrom(startDir) {
8948
9013
  let dir = startDir;
8949
9014
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
8950
- const candidate = join4(dir, "package.json");
9015
+ const candidate = join5(dir, "package.json");
8951
9016
  let pkg;
8952
9017
  try {
8953
9018
  pkg = JSON.parse(readFileSync2(candidate, "utf8"));
@@ -9158,6 +9223,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
9158
9223
  } else {
9159
9224
  const encoderName = codec === "h264" ? "libx264" : "libx265";
9160
9225
  args.push("-c:v", encoderName, "-preset", preset);
9226
+ appendSoftwareEncoderThreadsArg(args);
9161
9227
  if (bitrate) args.push("-b:v", bitrate);
9162
9228
  else args.push("-crf", String(quality));
9163
9229
  const gop = resolveLockedGopSize(options);
@@ -9280,7 +9346,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9280
9346
  const startTime = Date.now();
9281
9347
  const outputDir = dirname2(outputPath);
9282
9348
  if (!existsSync5(outputDir)) mkdirSync2(outputDir, { recursive: true });
9283
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9349
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9284
9350
  const frameCount = files.length;
9285
9351
  if (frameCount === 0) {
9286
9352
  return {
@@ -9296,7 +9362,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9296
9362
  if (options.useGpu) {
9297
9363
  gpuEncoder = await getCachedGpuEncoder();
9298
9364
  }
9299
- const inputPath = join5(framesDir, framePattern);
9365
+ const inputPath = frameSequencePath(framesDir, framePattern);
9300
9366
  const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
9301
9367
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
9302
9368
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG.ffmpegEncodeTimeout;
@@ -9352,7 +9418,7 @@ function buildConcatArgs(concatListPath, outputPath) {
9352
9418
  var concatListSeq = 0;
9353
9419
  function writeConcatList(dir, inputPaths) {
9354
9420
  concatListSeq += 1;
9355
- const listPath = join5(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9421
+ const listPath = join6(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9356
9422
  const body = inputPaths.map((p) => `file '${p.replace(/'/g, "'\\''")}'`).join("\n");
9357
9423
  writeFileSync2(listPath, body, "utf-8");
9358
9424
  return listPath;
@@ -9382,7 +9448,7 @@ async function concatVideoFiles(inputPaths, outputPath, signal, config) {
9382
9448
  }
9383
9449
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal, config, onFramesEncoded) {
9384
9450
  const start = Date.now();
9385
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9451
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9386
9452
  if (files.length === 0) {
9387
9453
  return {
9388
9454
  success: false,
@@ -9396,7 +9462,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9396
9462
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
9397
9463
  const chunkCount = Math.ceil(files.length / chunkSize);
9398
9464
  mkdirSync2(dirname2(outputPath), { recursive: true });
9399
- const chunkDir = mkdtempSync(join5(dirname2(outputPath), "chunk-encode-"));
9465
+ const chunkDir = mkdtempSync(join6(dirname2(outputPath), "chunk-encode-"));
9400
9466
  const chunkPaths = [];
9401
9467
  for (let i = 0; i < chunkCount; i++) {
9402
9468
  if (signal?.aborted) {
@@ -9412,8 +9478,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9412
9478
  const startNumber = i * chunkSize;
9413
9479
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
9414
9480
  const ext = outputPath.endsWith(".webm") ? ".webm" : outputPath.endsWith(".mov") ? ".mov" : ".mp4";
9415
- const chunkPath = join5(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
9416
- const inputPath = join5(framesDir, framePattern);
9481
+ const chunkPath = join6(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
9482
+ const inputPath = frameSequencePath(framesDir, framePattern);
9417
9483
  const inputArgs = [
9418
9484
  "-framerate",
9419
9485
  fpsToFfmpegArg(options.fps),
@@ -9582,14 +9648,14 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9582
9648
  "-master_pl_name",
9583
9649
  HLS_MASTER_PLAYLIST,
9584
9650
  "-hls_segment_filename",
9585
- join5(outputDir, "%v_%05d.ts"),
9651
+ join6(outputDir, "%v_%05d.ts"),
9586
9652
  // Without these the mpegts muxer starts the stream at PTS 1.4 s.
9587
9653
  "-muxdelay",
9588
9654
  "0",
9589
9655
  "-muxpreload",
9590
9656
  "0"
9591
9657
  );
9592
- args.push("-y", join5(outputDir, "%v.m3u8"));
9658
+ args.push("-y", join6(outputDir, "%v.m3u8"));
9593
9659
  const processTimeout = options.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
9594
9660
  const result = await runFfmpeg(args, { signal, timeout: processTimeout });
9595
9661
  if (signal?.aborted) {
@@ -9601,7 +9667,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9601
9667
  };
9602
9668
  }
9603
9669
  if (result.success && hasAudio) {
9604
- const masterPath = join5(outputDir, HLS_MASTER_PLAYLIST);
9670
+ const masterPath = join6(outputDir, HLS_MASTER_PLAYLIST);
9605
9671
  let master;
9606
9672
  try {
9607
9673
  master = openSync(masterPath, "r+", 384);
@@ -9862,6 +9928,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
9862
9928
  } else {
9863
9929
  const encoderName = codec === "h264" ? "libx264" : "libx265";
9864
9930
  args.push("-c:v", encoderName, "-preset", preset);
9931
+ appendSoftwareEncoderThreadsArg(args);
9865
9932
  if (bitrate) args.push("-b:v", bitrate);
9866
9933
  else args.push("-crf", String(quality));
9867
9934
  const lockedGop = resolveLockedGopSize(options);
@@ -10222,7 +10289,7 @@ function isKnownInactiveTimelineWindow(element, resolvedStart) {
10222
10289
  // ../engine/src/utils/alphaPlaneProbe.ts
10223
10290
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync } from "fs";
10224
10291
  import { tmpdir as tmpdir2 } from "os";
10225
- import { join as join6 } from "path";
10292
+ import { join as join7 } from "path";
10226
10293
  var BYTES_PER_SAMPLE_FRAME = 8 * 8 * 4;
10227
10294
  var MAX_SAMPLE_BYTES = BYTES_PER_SAMPLE_FRAME * 3;
10228
10295
  function sampledRgbaAlphaIsFullyOpaque(buf) {
@@ -10237,8 +10304,8 @@ function sampledRgbaAlphaIsFullyOpaque(buf) {
10237
10304
  async function probeInputAlphaPlane(videoPath, decoder, signal) {
10238
10305
  let probeDir;
10239
10306
  try {
10240
- probeDir = mkdtempSync2(join6(tmpdir2(), "hf-alpha-probe-"));
10241
- const samplePath = join6(probeDir, "alpha.raw");
10307
+ probeDir = mkdtempSync2(join7(tmpdir2(), "hf-alpha-probe-"));
10308
+ const samplePath = join7(probeDir, "alpha.raw");
10242
10309
  const result = await runFfmpeg(
10243
10310
  [
10244
10311
  "-v",
@@ -10284,7 +10351,7 @@ import {
10284
10351
  mkdirSync as mkdirSync4,
10285
10352
  lstatSync,
10286
10353
  openSync as openSync2,
10287
- readdirSync as readdirSync3,
10354
+ readdirSync as readdirSync4,
10288
10355
  rmdirSync,
10289
10356
  rmSync as rmSync2,
10290
10357
  statSync as statSync5,
@@ -10292,7 +10359,7 @@ import {
10292
10359
  } from "fs";
10293
10360
  import { createHash, randomUUID as randomUUID2 } from "crypto";
10294
10361
  import { BlockList, isIP } from "net";
10295
- import { dirname as dirname4, extname as extname2, join as join7 } from "path";
10362
+ import { dirname as dirname4, extname as extname2, join as join8 } from "path";
10296
10363
  import { Readable, Transform } from "stream";
10297
10364
  import { pipeline } from "stream/promises";
10298
10365
  var inFlightDownloads = /* @__PURE__ */ new Map();
@@ -10462,7 +10529,7 @@ function sameCacheLockStatGeneration(left, right) {
10462
10529
  function observeCachePathLock(lockPath) {
10463
10530
  for (let pass = 0; pass < 3; pass += 1) {
10464
10531
  const before = lstatSync(lockPath);
10465
- const owner = readdirSync3(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10532
+ const owner = readdirSync4(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10466
10533
  const after = lstatSync(lockPath);
10467
10534
  if (sameCacheLockStatGeneration(before, after)) return { stats: after, owner };
10468
10535
  }
@@ -10484,7 +10551,7 @@ function removeCacheLockDirectoryIfEmpty(lockPath) {
10484
10551
  }
10485
10552
  function releaseOwnedCachePathLock(lockPath, owner) {
10486
10553
  try {
10487
- rmdirSync(join7(lockPath, owner));
10554
+ rmdirSync(join8(lockPath, owner));
10488
10555
  } catch (error) {
10489
10556
  if (error.code === "ENOENT") return;
10490
10557
  throw error;
@@ -10525,12 +10592,12 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10525
10592
  if (createdLock) {
10526
10593
  const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
10527
10594
  try {
10528
- mkdirSync4(join7(lockPath, owner));
10529
- const entries = readdirSync3(lockPath);
10595
+ mkdirSync4(join8(lockPath, owner));
10596
+ const entries = readdirSync4(lockPath);
10530
10597
  if (entries.length === 1 && entries[0] === owner) {
10531
10598
  return () => releaseOwnedCachePathLock(lockPath, owner);
10532
10599
  }
10533
- rmdirSync(join7(lockPath, owner));
10600
+ rmdirSync(join8(lockPath, owner));
10534
10601
  removeCacheLockDirectoryIfEmpty(lockPath);
10535
10602
  continue;
10536
10603
  } catch (error) {
@@ -10548,7 +10615,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10548
10615
  if (Date.now() - observedLock.stats.mtimeMs > CACHE_LOCK_STALE_MS) {
10549
10616
  if (observedLock.owner) {
10550
10617
  try {
10551
- rmdirSync(join7(lockPath, observedLock.owner));
10618
+ rmdirSync(join8(lockPath, observedLock.owner));
10552
10619
  } catch (error) {
10553
10620
  if (error.code === "ENOENT") continue;
10554
10621
  throw error;
@@ -10556,7 +10623,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10556
10623
  removeCacheLockDirectoryIfEmpty(lockPath);
10557
10624
  continue;
10558
10625
  }
10559
- const reclaimPath = join7(lockPath, CACHE_LOCK_RECLAIM_NAME);
10626
+ const reclaimPath = join8(lockPath, CACHE_LOCK_RECLAIM_NAME);
10560
10627
  try {
10561
10628
  mkdirSync4(reclaimPath);
10562
10629
  } catch (error) {
@@ -10996,8 +11063,8 @@ function emitDownloadTelemetry(options, event) {
10996
11063
  }
10997
11064
  }
10998
11065
  async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, signal) {
10999
- const attemptDir = mkdtempSync3(join7(dirname4(localPath), ".hf-download-"));
11000
- const partialPath = join7(attemptDir, "payload");
11066
+ const attemptDir = mkdtempSync3(join8(dirname4(localPath), ".hf-download-"));
11067
+ const partialPath = join8(attemptDir, "payload");
11001
11068
  const controller = new AbortController();
11002
11069
  let timedOut = false;
11003
11070
  let callerAborted = signal?.aborted ?? false;
@@ -11233,7 +11300,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5, signal, onTransient
11233
11300
  mkdirSync4(destDir, { recursive: true });
11234
11301
  }
11235
11302
  const filename = getFilenameFromUrl(url, validationScope);
11236
- const localPath = join7(destDir, filename);
11303
+ const localPath = join8(destDir, filename);
11237
11304
  const downloadPromise = (async () => {
11238
11305
  const cacheStartedAt = Date.now();
11239
11306
  const reused = await reuseOrInvalidateCachedFile(
@@ -11317,56 +11384,6 @@ import {
11317
11384
  writeFileSync as writeFileSync3
11318
11385
  } from "fs";
11319
11386
  import { join as join9 } from "path";
11320
-
11321
- // ../engine/src/services/extractedFrameIndex.ts
11322
- import { readdirSync as readdirSync4 } from "fs";
11323
- import { join as join8 } from "path";
11324
- var FRAME_FILENAME_PREFIX = "frame_";
11325
- var ExtractedFrameSequenceError = class extends Error {
11326
- constructor(message) {
11327
- super(message);
11328
- this.name = "ExtractedFrameSequenceError";
11329
- }
11330
- };
11331
- function extractedFrameIndex(file, format) {
11332
- const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
11333
- if (!match) {
11334
- throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
11335
- }
11336
- const ordinal = Number.parseInt(match[1], 10);
11337
- if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
11338
- throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
11339
- }
11340
- return ordinal - 1;
11341
- }
11342
- function framePathsFromDirectory(outputDir, format, allowGaps = false) {
11343
- const suffix = `.${format}`;
11344
- const indexed = /* @__PURE__ */ new Map();
11345
- for (const file of readdirSync4(outputDir)) {
11346
- if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
11347
- const index = extractedFrameIndex(file, format);
11348
- if (indexed.has(index)) {
11349
- throw new ExtractedFrameSequenceError(
11350
- `Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
11351
- );
11352
- }
11353
- indexed.set(index, join8(outputDir, file));
11354
- }
11355
- if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
11356
- const ordered = /* @__PURE__ */ new Map();
11357
- for (let index = 0; index < indexed.size; index += 1) {
11358
- const path = indexed.get(index);
11359
- if (!path) {
11360
- throw new ExtractedFrameSequenceError(
11361
- `Missing extracted frame index ${index} in ${outputDir}`
11362
- );
11363
- }
11364
- ordered.set(index, path);
11365
- }
11366
- return ordered;
11367
- }
11368
-
11369
- // ../engine/src/services/extractionCache.ts
11370
11387
  var COMPLETE_SENTINEL = ".hf-complete";
11371
11388
  var GC_MARKER = ".hf-last-gc";
11372
11389
  var SCHEMA_PREFIX = "hfcache-v8-";
@@ -12118,7 +12135,7 @@ async function extractVideoFramesRangeUnredacted(videoPath, videoId, startTime,
12118
12135
  }
12119
12136
  const format = resolveFrameFormat(metadata, options.format);
12120
12137
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
12121
- const outputPattern = join10(videoOutputDir, framePattern);
12138
+ const outputPattern = frameSequencePath(videoOutputDir, framePattern);
12122
12139
  const isHdr = isHdrColorSpace(metadata.colorSpace);
12123
12140
  const isMacOS = process.platform === "darwin";
12124
12141
  const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
@@ -12463,6 +12480,7 @@ function resolveVideoExtractionDuration(video, metadata, timelineEnd) {
12463
12480
  return resolveVideoExtractionWindow(video, metadata, timelineEnd).durationSeconds;
12464
12481
  }
12465
12482
  var ALPHA_CAPABLE_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8", "prores"]);
12483
+ var SIDECAR_ALPHA_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8"]);
12466
12484
  function codecMayHaveAlpha(codec) {
12467
12485
  return ALPHA_CAPABLE_CODECS.has((codec ?? "").toLowerCase());
12468
12486
  }
@@ -12470,7 +12488,8 @@ function decoderForCodec(codec) {
12470
12488
  return alphaDecoderFor(codec) ?? (codec ?? "").toLowerCase();
12471
12489
  }
12472
12490
  function resolveFrameFormat(metadata, requested) {
12473
- if (metadata.hasAlpha || codecMayHaveAlpha(metadata.videoCodec)) return "png";
12491
+ if (metadata.hasAlpha || SIDECAR_ALPHA_CODECS.has((metadata.videoCodec ?? "").toLowerCase()))
12492
+ return "png";
12474
12493
  if (requested === "png" || requested === "jpg") return requested;
12475
12494
  return "jpg";
12476
12495
  }
@@ -14054,6 +14073,8 @@ async function encodeAac(input, outputPath, totalDuration, {
14054
14073
  segments.map(
14055
14074
  ({ trim, path }) => runFfmpeg(
14056
14075
  [
14076
+ "-cpuflags",
14077
+ "0",
14057
14078
  "-i",
14058
14079
  input,
14059
14080
  "-af",
@@ -14579,7 +14600,7 @@ import { join as join13 } from "path";
14579
14600
  import { pathToFileURL } from "url";
14580
14601
 
14581
14602
  // ../core/dist/generated/audio-fx-runtime-inline.js
14582
- var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var we="data-fx-chain",Ft=we.slice(5),W=1,F=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),C=(e=.707,t="Bandwidth: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),N=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[N(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[F("frequency","Frequency",1e3),N(-40,40,0),C(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[F("frequency","Frequency",200,20,2e3),N(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),N(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[F("frequency","Cutoff",300,20,2e4),C(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[F("frequency","Cutoff",8e3,100,2e4),C(.707),K],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Peak limiter that follows the signal\'s level. It has no lookahead, so a fast peak or an inter-sample peak can pass the ceiling; use the true-peak limiter for a delivery limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"truepeak",label:"True-peak limiter",group:"dynamics",description:"Lookahead limiter that holds a 4x estimate of the inter-sample peak at a dBTP ceiling; full-band noise can land up to about 1.7 dB above it. Adds lookahead plus about 0.3 ms of latency.",params:[{kind:"number",key:"ceiling",label:"Ceiling",unit:"dBTP",min:-24,max:0,step:.1,default:-1,hint:"Target true peak, measured on a 4x oversampled signal. Dense full-band sound can end up to about 1.7 dB above it."},{kind:"number",key:"lookahead",label:"Lookahead",unit:"ms",min:.5,max:10,step:.1,default:3,hint:"How far ahead the gain starts falling. Longer is smoother and adds the same latency."},{kind:"number",key:"release",label:"Release",unit:"ms",min:10,max:2e3,step:1,default:80,scale:"log"}],web:"worklet-truepeak"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times, a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],S=new Map(z.map(e=>[e.id,e]));function P(e){return S.get(e)}var Pt=z.map(e=>e.id);function y(e,t){let n=S.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(u=>u.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var x=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Chain file must be a JSON object.");let n=t;if(n.version!==W)throw new x(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new x("Chain file is missing a `nodes` array.");let o=n.nodes.map(ve);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function ve(e,t){if(typeof e!="object"||e===null)throw new x(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!S.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Te({type:n.type,id:E(n.id),fromCarve:Y(n.fromCarve),fromPreset:E(n.fromPreset),label:E(n.label),fromEq:E(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:Me(n.presetAmount),enabled:n.enabled!==!1,params:y(n.type,n.params??void 0)})}function E(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function Me(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function Te(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Fe(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Fe(e,t)+16}var Z=`\nconst TP_OS = 4;\nconst TP_HALF = 64;\nconst TP_BRANCH = 33;\nconst TP_DELAY = 16;\n\nfunction besselI0(x) {\n let sum = 1, term = 1;\n for (let k = 1; k < 40; k++) {\n term *= (x / (2 * k)) * (x / (2 * k));\n sum += term;\n }\n return sum;\n}\n\n/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */\nfunction tpBranches() {\n const beta = 6;\n const norm = besselI0(beta);\n const branches = [];\n for (let p = 0; p < TP_OS; p++) {\n const taps = new Float64Array(TP_BRANCH);\n let sum = 0;\n for (let j = 0; j < TP_BRANCH; j++) {\n const k = p + TP_OS * j;\n if (k > 2 * TP_HALF) continue;\n const t = (k - TP_HALF) / TP_OS;\n const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);\n const r = (k - TP_HALF) / TP_HALF;\n taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);\n sum += taps[j];\n }\n // Unity gain at DC on every branch, so a steady level reads as itself.\n for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;\n branches.push(taps);\n }\n return branches;\n}\n\nclass HfTruePeak extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));\n this.delayLen = this.look + TP_DELAY;\n this.branches = tpBranches();\n this.setLevels();\n this.clear();\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n if (e.data && e.data.__hfReset) { this.clear(); return; }\n this.p = { ...this.p, ...e.data };\n this.setLevels();\n };\n }\n /** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */\n clear() {\n this.ch = [];\n this.rel = 1;\n this.dqIdx = new Int32Array(this.look + 2);\n this.dqVal = new Float64Array(this.look + 2);\n this.dqHead = 0;\n this.dqTail = 0;\n this.t = 0;\n this.avgRing = new Float64Array(this.look + 1).fill(1);\n this.avgPos = 0;\n this.avgSum = this.look + 1;\n }\n setLevels() {\n this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);\n this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));\n }\n channel(c) {\n if (!this.ch[c]) {\n this.ch[c] = {\n delay: new Float32Array(this.delayLen),\n w: 0,\n hist: new Float32Array(TP_BRANCH * 2),\n pos: 0,\n };\n }\n return this.ch[c];\n }\n /** Largest interpolated magnitude across channels for the newest sample. */\n peak(chans, inputs, n) {\n let peak = 0;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n const x = inputs[c][n];\n s.pos = (s.pos + 1) % TP_BRANCH;\n s.hist[s.pos] = x;\n s.hist[s.pos + TP_BRANCH] = x;\n const base = s.pos + TP_BRANCH;\n for (let p = 0; p < TP_OS; p++) {\n const taps = this.branches[p];\n let acc = 0;\n for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];\n const a = acc < 0 ? -acc : acc;\n if (a > peak) peak = a;\n }\n }\n return peak;\n }\n /** Windowed minimum of the required gain over the last look + 1 values. */\n windowMin(v) {\n const cap = this.dqIdx.length;\n while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {\n this.dqTail = (this.dqTail + cap - 1) % cap;\n }\n this.dqIdx[this.dqTail] = this.t;\n this.dqVal[this.dqTail] = v;\n this.dqTail = (this.dqTail + 1) % cap;\n while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {\n this.dqHead = (this.dqHead + 1) % cap;\n }\n this.t++;\n return this.dqVal[this.dqHead];\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const chans = i.length;\n const frames = i[0].length;\n for (let n = 0; n < frames; n++) {\n const peak = this.peak(chans, i, n);\n const need = peak > this.ceiling ? this.ceiling / peak : 1;\n const m = this.windowMin(need);\n this.avgSum += m - this.avgRing[this.avgPos];\n this.avgRing[this.avgPos] = m;\n this.avgPos = (this.avgPos + 1) % this.avgRing.length;\n const target = Math.min(1, this.avgSum / this.avgRing.length);\n const released = this.rel + (1 - this.rel) * this.relCoef;\n this.rel = released < target ? released : target;\n if (target >= 1 && this.rel > 0.9999999) this.rel = 1;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n o[c][n] = s.delay[s.w] * this.rel;\n s.delay[s.w] = i[c][n];\n s.w = (s.w + 1) % this.delayLen;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-truepeak", HfTruePeak);\n`;var Pe=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left, so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.start = Math.round((this.p.__hfElapsed ?? 0) * sampleRate);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n // Primed from the input: a counter seeded mid-hold would otherwise play 0 until it wraps.\n if (this.held[ch] === undefined) this.held[ch] = Math.round(inp[0] * levels) / levels;\n // The hold counter runs from the file\'s first sample, so a node built part-way in starts where it would be.\n if (this.holds[ch] === undefined) this.holds[ch] = this.start % step;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point, which hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel). Advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n // The taps sweep at a constant rate from the start of the file, so a node built\n // part-way in joins the sweep where it would already be.\n const ratio0 = Math.pow(2, Math.max(-12, Math.min(12, this.p.semitones ?? 0)) / 12);\n const turns = (this.p.__hfElapsed ?? 0) * sampleRate * ((1 - ratio0) / this.grain);\n this.phase = turns - Math.floor(turns);\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros, so the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0. A one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal, while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`+Z,L=new WeakMap;var He=new WeakSet;function O(e){let t=L.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(Pe)))}`;await e.audioWorklet.addModule(n),He.add(e)})().catch(n=>{throw L.delete(e),n}),L.set(e,t)),t}function _e(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),u=Math.max(.001,1-Math.max(0,Math.min(1,n))),l=0,m=0;for(let c=0;c<a;c++){l+=u*(s()-l);let b=l*Math.pow(1-c/a,2.5);r[c]=b,m+=b*b}let p=Math.sqrt(m);if(p>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/p;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function ae(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let l=0;l<a;l++){let m=l/a;i[l]=t==="sine"?Math.sin(2*Math.PI*m):4*Math.abs((m+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let u=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,u),s}function oe(e){try{e.stop()}catch{}e.disconnect()}function I(e,t){return[{param:e},{param:t,map:n=>1-n}]}function re(e,t,n){e.gain.value=n,t.gain.value=1-n}function q(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var Re=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Ne=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Re.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function Ee(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o,a)=>{let r=new AudioWorkletNode(n,e,{processorOptions:{...o,__hfElapsed:a}});return{input:r,output:r,update:i=>r.port.postMessage({...i}),...t?{reset:()=>r.port.postMessage({__hfReset:!0})}:{},dispose:()=>{r.port.postMessage({__hfDispose:!0}),r.disconnect()}}}}var De={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},Ce=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=De[String(i.type)]??Math.tanh,u=Math.pow(10,f(i.threshold)/20),l=8192,m=new Float32Array(l);for(let p=0;p<l;p++){let c=p/(l-1)*2-1;m[p]=s(c/Math.max(1e-6,u))*u}n.curve=m,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Se=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let u=l=>{a.delayTime.value=Math.min(5,f(l.time)/1e3),r.gain.value=f(l.feedback),re(i,s,f(l.mix))};return u(t),{input:n,output:o,update:u,automation:{time:[{param:a.delayTime,map:l=>Math.min(5,B(l))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(l=>l.disconnect())}},Le=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),u=e.createGain(),l=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(u).connect(a),o.connect(l).connect(a);let m=p=>{r.delayTime.value=f(p.delay)/1e3,s.gain.value=f(p.depth)/1e3,i.playbackRate.value=f(p.speed),re(u,l,f(p.mix))};return m(t),{input:o,output:a,update:m,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(u.gain,l.gain)},dispose:()=>{oe(i),[o,a,r,s,u,l].forEach(p=>p.disconnect())}}},Oe=6,Be=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=ae(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),u=e.createGain(),l=e.createGain(),m=e.createGain(),p=[];o.connect(r);let c=r;for(let d=0;d<Oe;d++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,u.connect(h.frequency),c.connect(h),c=h,p.push(h)}s.connect(u),c.connect(l).connect(i),r.connect(m).connect(i),i.connect(a);let b=d=>{let h=1e3/Math.max(.1,f(d.delay));for(let g of p)g.frequency.value=h;u.gain.value=h*f(d.decay),s.playbackRate.value=f(d.speed),r.gain.value=f(d.in_gain),i.gain.value=f(d.out_gain),l.gain.value=1,m.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{oe(s),[o,a,r,i,u,l,m,...p].forEach(d=>d.disconnect())}}},Ie=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",u=l=>{let m=`${f(l.size)}:${f(l.damping)}`;if(m!==s){let p=_e(e.sampleRate,f(l.size),f(l.damping)),c=e.createBuffer(1,p.length,e.sampleRate);c.getChannelData(0).set(p),a.buffer=c,s=m}r.gain.value=f(l.wet),i.gain.value=f(l.dry)};return u(t),{input:n,output:o,update:u,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(l=>l.disconnect())}},qe={"gain-node":Ne,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Se,"chorus-lfo":Le,"allpass-phaser":Be,convolver:Ie};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ge(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return Ee(t)(e,r,o);let i=qe[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var p;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),u=o;for(let c of s){let b=null;if(c.preset){let d=e.createGain(),h=e.createGain(),g=e.createGain(),T=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,u.connect(d),d.connect(h).connect(T),b={entry:d,wet:g,dry:h,join:T},u=d}for(let d of c.nodes){let h=Ge(e,d.type,d.params??{},n);u.connect(h.input),u=h.output,r.push({...d.id?{id:d.id}:{},type:d.type,handle:h})}b&&c.preset&&(u.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),u=b.join)}u.connect(a);let l=ne(t),m={};for(let c of i)(m[p=c.id]??(m[p]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:m,nodes:r,update(c){if(ne(c)!==l)return!1;w(c).forEach((d,h)=>{let g=r[h];g&&(g.handle.update(y(d.type,d.params)),d.id===void 0?delete g.id:g.id=d.id)});let b=0;for(let d of te(w(c))){if(!d.preset)continue;let h=i[b++];!h||h.id!==d.preset||(h.wet.gain.value=d.amount,h.dry.gain.value=1-d.amount)}return!0},reset(){for(let{handle:c}of r)c.reset?.()},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:d,join:h}of i)c.disconnect(),b.disconnect(),d.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,U=10;var Ve="data-automation",Vt=Ve.slice(5),D=1,$e=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},V="volume",le="rate";function R(e){if(e===V)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===je){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var je="preset";var Xe={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1},me={min:G,max:U,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=R(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return me;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...Xe,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function We(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function pe(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ke(e){let t=We(e?.curve),n=pe(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function Ye(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ke(e)}}function he(e,t){let n=e.map(a=>Ye(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,$e)}function ze(e){let t=[];for(let n of e.lanes){if(!R(n.target))continue;let o=n.target===V?de:n.target===le?me:null,a=he(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function fe(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=he(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function be(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!R(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return ze({version:D,lanes:o})}function Je(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Qe(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,u=Math.min(r,Math.max(1,i,s));return{cx:e+n/u,cy:t+o/u,w:u}}function Ze(e,t,n){let{cx:o,cy:a,w:r}=Qe(t,n),i=2-2*r,s=e*i-1+2*r*o,u=-e*i-2*r*o,l=et(s,u,e),m=1-l,p=m*m+2*r*l*m+l*l;return p>0?(2*r*a*l*m+l*l)/p:e}function et(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function tt(e,t){let n=pe(t.viaX,t.viaY);return n?Ze(Math.min(1,Math.max(0,e)),n.x,n.y):Je(e,t.curve)}function nt(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function j(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let p=i+s>>1;o[p].t<=t?i=p:s=p}let u=o[i],l=o[s],m=l.t-u.t;return m<=0?l.v:nt(u.v,l.v,tt((t-u.t)/m,u),n)}function ge(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function Ae(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let u=0;u<r;u+=1)i[u]=j(e,t+s*u/(r-1),a);return i}var at=100,ot=8,rt=4096,X=e=>e;function it(e,t,n){return!(!!e.curve||e.viaX!==void 0&&e.viaY!==void 0)&&t==="linear"&&!n}function st(e){let t=Math.ceil(e*at);return Math.min(rt,Math.max(ot,t))}function ut(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,u=a??X,l=v=>r+(v-i)/s,m=e.points[t],p=e.points[t+1],c=l(p.t);if(c<=r)return null;let b=Math.max(r,l(m.t)),d=c-b;if(d<=0)return null;if(it(m,n,a))return{kind:"ramp",time:c,value:u(p.v)};let h=st(d),g=Ae(e,Math.max(m.t,i),p.t,h,n),T=new Float32Array(h);for(let v=0;v<h;v+=1)T[v]=u(g[v]??0);return{kind:"curve",time:b,duration:d,values:T}}function lt(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=ut(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??X)(j(e,n.elapsed,t))}),a}function ct(e,t){e.cancelScheduledValues(0);for(let n of t)n.kind==="set"?e.setValueAtTime(n.value,n.time):n.kind==="ramp"?e.linearRampToValueAtTime(n.value,n.time):(e.setValueAtTime(n.values[0]??0,n.time),e.linearRampToValueAtTime(n.values[n.values.length-1]??0,n.time+n.duration))}function dt(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{ct(e,t);return}}function mt(e){for(let t of e)t.param.cancelScheduledValues(0)}function pt(e,t,n,o){if(t.points.length!==0)for(let a of e){if(mt([a]),ge(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}dt(a.param,lt(t,n,o,a.map))}}function xe(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let u=R(s.target);if(!u)continue;let l=u.kind==="preset"?a?.[u.presetId]:u.kind==="fx"?r.get(u.nodeId)?.automation?.[u.param]:void 0;if(!l||l.length===0)continue;let m=$(s.target,t);m&&(pt(l,s,m.scale,o),i.push(...l))}return i}var ht=5,ft=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function bt(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(ft)/Math.log(o))*n}function gt(e){return .6+Math.max(0,Math.min(1,e))*2.6}function At(e,t){return k(e,"wet",t)>0?gt(k(e,"size",t)):0}function xt(e,t){return k(e,"mix",t)>0?bt(k(e,"time",t),k(e,"feedback",t)):0}function yt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function kt(e,t){return k(e,"mix",t)>0?.2:0}function wt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return At(e,t);case"delay":return xt(e,t);case"chorus":return yt(e,t);case"pitchshift":return kt(e,t);default:return 0}}function ye(e,t){let n=e.nodes.reduce((o,a)=>o+wt(a,t),0);return Math.min(ht,n)}function ke(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function vt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function Mt(e,t){let n=[];for(let o=0;o<e.numberOfChannels;o++)n.push(new Float32Array(e.getChannelData(o).subarray(t)));return n}async function Tt(e,t,n,o,a=0){let r=J(n),i=Math.max(1,e.length),s=e[0]?.length??0;if(s===0)return e;let u=o?fe(be(o),r):null,l=Math.ceil(ye(r,u??void 0)*t),m=ke(r,t),p=s+l+m,c=new OfflineAudioContext(i,p,t);ie(r)&&await O(c);let b=c.createBufferSource();b.buffer=vt(c,e,i,p,t);let d=se(c,r,a);return u&&xe(u,r,d.nodes,{scheduledAt:0,elapsed:0,rate:1},d.presets),b.connect(d.input),d.output.connect(c.destination),b.start(),Mt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Tt};})();\n';
14603
+ var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var ve="data-fx-chain",Pt=ve.slice(5),W=1,F=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),S=(e=.707,t="Bandwidth: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),N=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[N(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[F("frequency","Frequency",1e3),N(-40,40,0),S(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[F("frequency","Frequency",200,20,2e3),N(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),N(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[F("frequency","Cutoff",300,20,2e4),S(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[F("frequency","Cutoff",8e3,100,2e4),S(.707),K],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Peak limiter that follows the signal\'s level. It has no lookahead, so a fast peak or an inter-sample peak can pass the ceiling; use the true-peak limiter for a delivery limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"truepeak",label:"True-peak limiter",group:"dynamics",description:"Lookahead limiter that holds a 4x estimate of the inter-sample peak at a dBTP ceiling; full-band noise can land up to about 1.7 dB above it. Adds lookahead plus about 0.3 ms of latency.",params:[{kind:"number",key:"ceiling",label:"Ceiling",unit:"dBTP",min:-24,max:0,step:.1,default:-1,hint:"Target true peak, measured on a 4x oversampled signal. Dense full-band sound can end up to about 1.7 dB above it."},{kind:"number",key:"lookahead",label:"Lookahead",unit:"ms",min:.5,max:10,step:.1,default:3,hint:"How far ahead the gain starts falling. Longer is smoother and adds the same latency."},{kind:"number",key:"release",label:"Release",unit:"ms",min:10,max:2e3,step:1,default:80,scale:"log"}],web:"worklet-truepeak"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times, a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],C=new Map(z.map(e=>[e.id,e]));function P(e){return C.get(e)}var Ht=z.map(e=>e.id);function y(e,t){let n=C.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(u=>u.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var x=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Chain file must be a JSON object.");let n=t;if(n.version!==W)throw new x(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new x("Chain file is missing a `nodes` array.");let o=n.nodes.map(Me);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function Me(e,t){if(typeof e!="object"||e===null)throw new x(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!C.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Fe({type:n.type,id:E(n.id),fromCarve:Y(n.fromCarve),fromPreset:E(n.fromPreset),label:E(n.label),fromEq:E(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:Te(n.presetAmount),enabled:n.enabled!==!1,params:y(n.type,n.params??void 0)})}function E(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function Te(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function Fe(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Pe(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Pe(e,t)+16}var Z=`\nconst TP_OS = 4;\nconst TP_HALF = 64;\nconst TP_BRANCH = 33;\nconst TP_DELAY = 16;\n\nfunction besselI0(x) {\n let sum = 1, term = 1;\n for (let k = 1; k < 40; k++) {\n term *= (x / (2 * k)) * (x / (2 * k));\n sum += term;\n }\n return sum;\n}\n\n/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */\nfunction tpBranches() {\n const beta = 6;\n const norm = besselI0(beta);\n const branches = [];\n for (let p = 0; p < TP_OS; p++) {\n const taps = new Float64Array(TP_BRANCH);\n let sum = 0;\n for (let j = 0; j < TP_BRANCH; j++) {\n const k = p + TP_OS * j;\n if (k > 2 * TP_HALF) continue;\n const t = (k - TP_HALF) / TP_OS;\n const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);\n const r = (k - TP_HALF) / TP_HALF;\n taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);\n sum += taps[j];\n }\n // Unity gain at DC on every branch, so a steady level reads as itself.\n for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;\n branches.push(taps);\n }\n return branches;\n}\n\nclass HfTruePeak extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));\n this.delayLen = this.look + TP_DELAY;\n this.branches = tpBranches();\n this.setLevels();\n this.clear();\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n if (e.data && e.data.__hfReset) { this.clear(); return; }\n this.p = { ...this.p, ...e.data };\n this.setLevels();\n };\n }\n /** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */\n clear() {\n this.ch = [];\n this.rel = 1;\n this.dqIdx = new Int32Array(this.look + 2);\n this.dqVal = new Float64Array(this.look + 2);\n this.dqHead = 0;\n this.dqTail = 0;\n this.t = 0;\n this.avgRing = new Float64Array(this.look + 1).fill(1);\n this.avgPos = 0;\n this.avgSum = this.look + 1;\n }\n setLevels() {\n this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);\n this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));\n }\n channel(c) {\n if (!this.ch[c]) {\n this.ch[c] = {\n delay: new Float32Array(this.delayLen),\n w: 0,\n hist: new Float32Array(TP_BRANCH * 2),\n pos: 0,\n };\n }\n return this.ch[c];\n }\n /** Largest interpolated magnitude across channels for the newest sample. */\n peak(chans, inputs, n) {\n let peak = 0;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n const x = inputs[c][n];\n s.pos = (s.pos + 1) % TP_BRANCH;\n s.hist[s.pos] = x;\n s.hist[s.pos + TP_BRANCH] = x;\n const base = s.pos + TP_BRANCH;\n for (let p = 0; p < TP_OS; p++) {\n const taps = this.branches[p];\n let acc = 0;\n for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];\n const a = acc < 0 ? -acc : acc;\n if (a > peak) peak = a;\n }\n }\n return peak;\n }\n /** Windowed minimum of the required gain over the last look + 1 values. */\n windowMin(v) {\n const cap = this.dqIdx.length;\n while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {\n this.dqTail = (this.dqTail + cap - 1) % cap;\n }\n this.dqIdx[this.dqTail] = this.t;\n this.dqVal[this.dqTail] = v;\n this.dqTail = (this.dqTail + 1) % cap;\n while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {\n this.dqHead = (this.dqHead + 1) % cap;\n }\n this.t++;\n return this.dqVal[this.dqHead];\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const chans = i.length;\n const frames = i[0].length;\n for (let n = 0; n < frames; n++) {\n const peak = this.peak(chans, i, n);\n const need = peak > this.ceiling ? this.ceiling / peak : 1;\n const m = this.windowMin(need);\n this.avgSum += m - this.avgRing[this.avgPos];\n this.avgRing[this.avgPos] = m;\n this.avgPos = (this.avgPos + 1) % this.avgRing.length;\n const target = Math.min(1, this.avgSum / this.avgRing.length);\n const released = this.rel + (1 - this.rel) * this.relCoef;\n this.rel = released < target ? released : target;\n if (target >= 1 && this.rel > 0.9999999) this.rel = 1;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n o[c][n] = s.delay[s.w] * this.rel;\n s.delay[s.w] = i[c][n];\n s.w = (s.w + 1) % this.delayLen;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-truepeak", HfTruePeak);\n`;var He=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left, so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.start = Math.round((this.p.__hfElapsed ?? 0) * sampleRate);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n // Primed from the input: a counter seeded mid-hold would otherwise play 0 until it wraps.\n if (this.held[ch] === undefined) this.held[ch] = Math.round(inp[0] * levels) / levels;\n // The hold counter runs from the file\'s first sample, so a node built part-way in starts where it would be.\n if (this.holds[ch] === undefined) this.holds[ch] = this.start % step;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point, which hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel). Advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n // The taps sweep at a constant rate from the start of the file, so a node built\n // part-way in joins the sweep where it would already be.\n const ratio0 = Math.pow(2, Math.max(-12, Math.min(12, this.p.semitones ?? 0)) / 12);\n const turns = (this.p.__hfElapsed ?? 0) * sampleRate * ((1 - ratio0) / this.grain);\n this.phase = turns - Math.floor(turns);\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros, so the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0. A one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal, while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`+Z,L=new WeakMap;var _e=new WeakSet;function O(e){let t=L.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(He)))}`;await e.audioWorklet.addModule(n),_e.add(e)})().catch(n=>{throw L.delete(e),n}),L.set(e,t)),t}function Re(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),u=Math.max(.001,1-Math.max(0,Math.min(1,n))),l=0,m=0;for(let c=0;c<a;c++){l+=u*(s()-l);let b=l*Math.pow(1-c/a,2.5);r[c]=b,m+=b*b}let p=Math.sqrt(m);if(p>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/p;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function ae(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let l=0;l<a;l++){let m=l/a;i[l]=t==="sine"?Math.sin(2*Math.PI*m):4*Math.abs((m+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let u=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,u),s}function oe(e){try{e.stop()}catch{}e.disconnect()}function I(e,t){return[{param:e},{param:t,map:n=>1-n}]}function re(e,t,n){e.gain.value=n,t.gain.value=1-n}function q(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var Ne=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Ee=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Ne.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function De(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o,a)=>{let r=new AudioWorkletNode(n,e,{processorOptions:{...o,__hfElapsed:a}});return{input:r,output:r,update:i=>r.port.postMessage({...i}),...t?{reset:()=>r.port.postMessage({__hfReset:!0})}:{},dispose:()=>{r.port.postMessage({__hfDispose:!0}),r.disconnect()}}}}var Se={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},Ce=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=Se[String(i.type)]??Math.tanh,u=Math.pow(10,f(i.threshold)/20),l=8192,m=new Float32Array(l);for(let p=0;p<l;p++){let c=p/(l-1)*2-1;m[p]=s(c/Math.max(1e-6,u))*u}n.curve=m,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Le=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let u=l=>{a.delayTime.value=Math.min(5,f(l.time)/1e3),r.gain.value=f(l.feedback),re(i,s,f(l.mix))};return u(t),{input:n,output:o,update:u,automation:{time:[{param:a.delayTime,map:l=>Math.min(5,B(l))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(l=>l.disconnect())}},Oe=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),u=e.createGain(),l=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(u).connect(a),o.connect(l).connect(a);let m=p=>{r.delayTime.value=f(p.delay)/1e3,s.gain.value=f(p.depth)/1e3,i.playbackRate.value=f(p.speed),re(u,l,f(p.mix))};return m(t),{input:o,output:a,update:m,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(u.gain,l.gain)},dispose:()=>{oe(i),[o,a,r,s,u,l].forEach(p=>p.disconnect())}}},Be=6,Ie=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=ae(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),u=e.createGain(),l=e.createGain(),m=e.createGain(),p=[];o.connect(r);let c=r;for(let d=0;d<Be;d++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,u.connect(h.frequency),c.connect(h),c=h,p.push(h)}s.connect(u),c.connect(l).connect(i),r.connect(m).connect(i),i.connect(a);let b=d=>{let h=1e3/Math.max(.1,f(d.delay));for(let g of p)g.frequency.value=h;u.gain.value=h*f(d.decay),s.playbackRate.value=f(d.speed),r.gain.value=f(d.in_gain),i.gain.value=f(d.out_gain),l.gain.value=1,m.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{oe(s),[o,a,r,i,u,l,m,...p].forEach(d=>d.disconnect())}}},qe=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",u=l=>{let m=`${f(l.size)}:${f(l.damping)}`;if(m!==s){let p=Re(e.sampleRate,f(l.size),f(l.damping)),c=e.createBuffer(1,p.length,e.sampleRate);c.getChannelData(0).set(p),a.buffer=c,s=m}r.gain.value=f(l.wet),i.gain.value=f(l.dry)};return u(t),{input:n,output:o,update:u,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(l=>l.disconnect())}},Ge={"gain-node":Ee,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Le,"chorus-lfo":Oe,"allpass-phaser":Ie,convolver:qe};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ve(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return De(t)(e,r,o);let i=Ge[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var p;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),u=o;for(let c of s){let b=null;if(c.preset){let d=e.createGain(),h=e.createGain(),g=e.createGain(),T=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,u.connect(d),d.connect(h).connect(T),b={entry:d,wet:g,dry:h,join:T},u=d}for(let d of c.nodes){let h=Ve(e,d.type,d.params??{},n);u.connect(h.input),u=h.output,r.push({...d.id?{id:d.id}:{},type:d.type,handle:h})}b&&c.preset&&(u.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),u=b.join)}u.connect(a);let l=ne(t),m={};for(let c of i)(m[p=c.id]??(m[p]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:m,nodes:r,update(c){if(ne(c)!==l)return!1;w(c).forEach((d,h)=>{let g=r[h];g&&(g.handle.update(y(d.type,d.params)),d.id===void 0?delete g.id:g.id=d.id)});let b=0;for(let d of te(w(c))){if(!d.preset)continue;let h=i[b++];!h||h.id!==d.preset||(h.wet.gain.value=d.amount,h.dry.gain.value=1-d.amount)}return!0},reset(){for(let{handle:c}of r)c.reset?.()},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:d,join:h}of i)c.disconnect(),b.disconnect(),d.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,V=10;var $e="data-automation",Xt=$e.slice(5),D=1,je=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},U="volume",le="rate";function R(e){if(e===U)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===Xe){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var Xe="preset";var We={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1};function me(e){return!!e.curve||e.viaX!==void 0&&e.viaY!==void 0}var pe={min:G,max:V,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=R(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return pe;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...We,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function Ke(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function he(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ye(e){let t=Ke(e?.curve),n=he(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function ze(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ye(e)}}function fe(e,t){let n=e.map(a=>ze(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,je)}function Je(e){let t=[];for(let n of e.lanes){if(!R(n.target))continue;let o=n.target===U?de:n.target===le?pe:null,a=fe(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function be(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=fe(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function ge(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!R(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return Je({version:D,lanes:o})}function Qe(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Ze(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,u=Math.min(r,Math.max(1,i,s));return{cx:e+n/u,cy:t+o/u,w:u}}function et(e,t,n){let{cx:o,cy:a,w:r}=Ze(t,n),i=2-2*r,s=e*i-1+2*r*o,u=-e*i-2*r*o,l=tt(s,u,e),m=1-l,p=m*m+2*r*l*m+l*l;return p>0?(2*r*a*l*m+l*l)/p:e}function tt(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function nt(e,t){let n=he(t.viaX,t.viaY);return n?et(Math.min(1,Math.max(0,e)),n.x,n.y):Qe(e,t.curve)}function at(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function j(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let p=i+s>>1;o[p].t<=t?i=p:s=p}let u=o[i],l=o[s],m=l.t-u.t;return m<=0?l.v:at(u.v,l.v,nt((t-u.t)/m,u),n)}function Ae(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function xe(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let u=0;u<r;u+=1)i[u]=j(e,t+s*u/(r-1),a);return i}var ot=100,rt=8,it=4096,X=e=>e;function st(e,t,n){return!me(e)&&t==="linear"&&!n}function ut(e){let t=Math.ceil(e*ot);return Math.min(it,Math.max(rt,t))}function lt(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,u=a??X,l=v=>r+(v-i)/s,m=e.points[t],p=e.points[t+1],c=l(p.t);if(c<=r)return null;let b=Math.max(r,l(m.t)),d=c-b;if(d<=0)return null;if(st(m,n,a))return{kind:"ramp",time:c,value:u(p.v)};let h=ut(d),g=xe(e,Math.max(m.t,i),p.t,h,n),T=new Float32Array(h);for(let v=0;v<h;v+=1)T[v]=u(g[v]??0);return{kind:"curve",time:b,duration:d,values:T}}function ct(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=lt(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??X)(j(e,n.elapsed,t))}),a}function dt(e,t){e.cancelScheduledValues(0);for(let n of t)n.kind==="set"?e.setValueAtTime(n.value,n.time):n.kind==="ramp"?e.linearRampToValueAtTime(n.value,n.time):(e.setValueAtTime(n.values[0]??0,n.time),e.linearRampToValueAtTime(n.values[n.values.length-1]??0,n.time+n.duration))}function mt(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{dt(e,t);return}}function pt(e){for(let t of e)t.param.cancelScheduledValues(0)}function ht(e,t,n,o){if(t.points.length!==0)for(let a of e){if(pt([a]),Ae(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}mt(a.param,ct(t,n,o,a.map))}}function ye(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let u=R(s.target);if(!u)continue;let l=u.kind==="preset"?a?.[u.presetId]:u.kind==="fx"?r.get(u.nodeId)?.automation?.[u.param]:void 0;if(!l||l.length===0)continue;let m=$(s.target,t);m&&(ht(l,s,m.scale,o),i.push(...l))}return i}var ft=5,bt=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function gt(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(bt)/Math.log(o))*n}function At(e){return .6+Math.max(0,Math.min(1,e))*2.6}function xt(e,t){return k(e,"wet",t)>0?At(k(e,"size",t)):0}function yt(e,t){return k(e,"mix",t)>0?gt(k(e,"time",t),k(e,"feedback",t)):0}function kt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function wt(e,t){return k(e,"mix",t)>0?.2:0}function vt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return xt(e,t);case"delay":return yt(e,t);case"chorus":return kt(e,t);case"pitchshift":return wt(e,t);default:return 0}}function ke(e,t){let n=e.nodes.reduce((o,a)=>o+vt(a,t),0);return Math.min(ft,n)}function we(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function Mt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function Tt(e,t){let n=[];for(let o=0;o<e.numberOfChannels;o++)n.push(new Float32Array(e.getChannelData(o).subarray(t)));return n}async function Ft(e,t,n,o,a=0){let r=J(n),i=Math.max(1,e.length),s=e[0]?.length??0;if(s===0)return e;let u=o?be(ge(o),r):null,l=Math.ceil(ke(r,u??void 0)*t),m=we(r,t),p=s+l+m,c=new OfflineAudioContext(i,p,t);ie(r)&&await O(c);let b=c.createBufferSource();b.buffer=Mt(c,e,i,p,t);let d=se(c,r,a);return u&&ye(u,r,d.nodes,{scheduledAt:0,elapsed:0,rate:1},d.presets),b.connect(d.input),d.output.connect(c.destination),b.start(),Tt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Ft};})();\n';
14583
14604
  function getAudioFxRuntimeScript() {
14584
14605
  return AUDIO_FX_RUNTIME_IIFE;
14585
14606
  }
@@ -14765,6 +14786,9 @@ function applyEnvelopeToPlanes(planes, sampleRate, gainAt, startFrame = 0) {
14765
14786
  for (const plane of planes) plane[frame] = (plane[frame] ?? 0) * gain;
14766
14787
  }
14767
14788
  }
14789
+ function applyInputGain(planes, gain = 1) {
14790
+ if (gain !== 1) applyEnvelopeToPlanes(planes, 1, () => gain);
14791
+ }
14768
14792
  var DEFAULT_STREAMING_LIMITS = {
14769
14793
  aboveBytes: 512 * 1024 * 1024,
14770
14794
  segmentSeconds: 300,
@@ -14838,6 +14862,7 @@ async function renderInSegments(openPage, inputWav, header, chain, outputWav, op
14838
14862
  throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
14839
14863
  }
14840
14864
  const planes = deinterleave(readFrames(inFd, header, seg.readFrom, seg.readTo), channels);
14865
+ applyInputGain(planes, options.inputGain);
14841
14866
  const page = await openPage();
14842
14867
  let outPlanes;
14843
14868
  try {
@@ -14897,6 +14922,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
14897
14922
  }
14898
14923
  const input = readWav(inputWav);
14899
14924
  const planes = deinterleave(input.samples, input.channels);
14925
+ applyInputGain(planes, options.inputGain);
14900
14926
  if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
14901
14927
  return withFxBrowser(options, async (openPage) => {
14902
14928
  const page = await openPage();
@@ -15223,7 +15249,8 @@ function boundedDetail(message, maxLength = 2e3) {
15223
15249
  /(^|[\s"'(])\/.+?(?=:\s[A-Z]|\s(?:ENOENT|EACCES|EPERM)\b|\r?$)/gm,
15224
15250
  "$1<redacted-path>"
15225
15251
  );
15226
- return redacted.length <= maxLength ? redacted : `${redacted.slice(0, maxLength - 1)}\u2026`;
15252
+ const head = Math.floor(maxLength / 4);
15253
+ return redacted.length <= maxLength ? redacted : `${redacted.slice(0, head)}\u2026${redacted.slice(-(maxLength - head - 1))}`;
15227
15254
  }
15228
15255
  function probeFailure(message, elementId) {
15229
15256
  const unavailable = /(?:not found|ENOENT|spawn)/i.test(message);
@@ -15256,7 +15283,7 @@ function downloadFailure(error, elementId) {
15256
15283
  detail: boundedDetail(`Download failed for audio element ${elementId}: ${message}`)
15257
15284
  };
15258
15285
  }
15259
- function ffmpegFailure(stage, result, elementId) {
15286
+ function ffmpegFailure(stage, result, timeoutMs, elementId) {
15260
15287
  const stderr = result.stderr ?? "";
15261
15288
  let reason = "ffmpeg_failed";
15262
15289
  let owner = "system";
@@ -15287,32 +15314,32 @@ function ffmpegFailure(stage, result, elementId) {
15287
15314
  elementId,
15288
15315
  subprocess: audioSubprocessEvidence(result),
15289
15316
  detail: boundedDetail(
15290
- result.error?.message ? `${formatFfmpegError(result.exitCode, stderr)}: ${result.error.message}` : formatFfmpegError(result.exitCode, stderr)
15317
+ `(ran ${result.durationMs} ms, limit ${timeoutMs} ms) ` + (result.error?.message ? `${describeFfmpegFailure(result, timeoutMs)}: ${result.error.message}` : describeFfmpegFailure(result, timeoutMs))
15291
15318
  )
15292
15319
  };
15293
15320
  }
15294
- var CURVED_SEGMENT_SAMPLES_PER_SEC = 30;
15295
- function volumeLaneKeyframes(automation, trackStart, duration) {
15321
+ function volumeLaneKeyframes(automation, trackStart, duration, gain, gainAhead = false) {
15296
15322
  const lane = automation.lanes.find((l) => l.target === VOLUME_TARGET);
15297
15323
  if (!lane || lane.points.length === 0) return null;
15324
+ const points = gainedVolumePoints(lane, gain, gainAhead);
15298
15325
  const out = [];
15299
15326
  const push = (t, v) => {
15300
15327
  out.push({ time: trackStart + t, volume: v });
15301
15328
  };
15302
- const first = lane.points[0];
15329
+ const first = points[0];
15303
15330
  if (first.t > 0) push(0, first.v);
15304
- for (let i = 0; i < lane.points.length; i += 1) {
15305
- const a = lane.points[i];
15331
+ for (let i = 0; i < points.length; i += 1) {
15332
+ const a = points[i];
15306
15333
  push(a.t, a.v);
15307
- const b = lane.points[i + 1];
15308
- if (!b || !a.curve) continue;
15309
- const steps = Math.max(2, Math.ceil((b.t - a.t) * CURVED_SEGMENT_SAMPLES_PER_SEC));
15334
+ const b = points[i + 1];
15335
+ if (!b || !isBentSegment(a)) continue;
15336
+ const steps = volumeSegmentSampleCount(b.t - a.t);
15310
15337
  for (let k = 1; k < steps; k += 1) {
15311
15338
  const t = a.t + (b.t - a.t) * k / steps;
15312
- push(t, sampleAutomationLane(lane, t));
15339
+ push(t, gainedVolume(sampleAutomationLane(lane, t), gain, gainAhead));
15313
15340
  }
15314
15341
  }
15315
- const last = lane.points[lane.points.length - 1];
15342
+ const last = points[points.length - 1];
15316
15343
  if (duration > last.t) push(duration, last.v);
15317
15344
  return out;
15318
15345
  }
@@ -15429,7 +15456,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
15429
15456
  failure: failure2
15430
15457
  };
15431
15458
  }
15432
- let failure = result.success ? void 0 : ffmpegFailure(stage, result);
15459
+ let failure = result.success ? void 0 : ffmpegFailure(stage, result, ffmpegProcessTimeout);
15433
15460
  const probedLinkFailedToRead = readsUrl && (failure?.reason === "invalid_media" || failure?.reason === "ffmpeg_failed");
15434
15461
  if (failure && probedLinkFailedToRead) {
15435
15462
  failure = { ...failure, reason: "download_failed", owner: "system", retryable: true };
@@ -15475,7 +15502,7 @@ async function generateSilence(outputPath, duration, signal, config) {
15475
15502
  failure: failure2
15476
15503
  };
15477
15504
  }
15478
- const failure = !result.success ? ffmpegFailure("silence", result) : void 0;
15505
+ const failure = !result.success ? ffmpegFailure("silence", result, ffmpegProcessTimeout) : void 0;
15479
15506
  return {
15480
15507
  success: result.success,
15481
15508
  outputPath,
@@ -15484,8 +15511,13 @@ async function generateSilence(outputPath, duration, signal, config) {
15484
15511
  failure
15485
15512
  };
15486
15513
  }
15487
- async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config) {
15488
- const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
15514
+ var MIX_TIMEOUT_MS_PER_FILM_SECOND = 100;
15515
+ function mixProcessTimeout(config, totalDuration) {
15516
+ const configured = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
15517
+ return Math.max(configured, Math.ceil(totalDuration * MIX_TIMEOUT_MS_PER_FILM_SECOND));
15518
+ }
15519
+ async function mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, config) {
15520
+ const ffmpegProcessTimeout = mixProcessTimeout(config, totalDuration);
15489
15521
  const masterOutputGain = config?.audioGain ?? DEFAULT_CONFIG.audioGain;
15490
15522
  if (tracks.length === 0) {
15491
15523
  const result2 = await generateSilence(outputPath, totalDuration, signal, config);
@@ -15584,7 +15616,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
15584
15616
  };
15585
15617
  }
15586
15618
  if (!result.success) {
15587
- const failure = ffmpegFailure("mix", result);
15619
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15588
15620
  return {
15589
15621
  success: false,
15590
15622
  outputPath,
@@ -15605,18 +15637,19 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
15605
15637
  function groupNormalizeOptionUnsupported(stderr) {
15606
15638
  return /normalize/i.test(stderr) && /(?:unrecognized option|option (?:was )?not found|no option name)/i.test(stderr);
15607
15639
  }
15608
- async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal, config) {
15609
- const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
15640
+ async function mixGroupMembersFlat(memberTracks, outputPath, totalDuration, signal, config) {
15641
+ const ffmpegProcessTimeout = mixProcessTimeout(config, totalDuration);
15610
15642
  const outputDir = dirname7(outputPath);
15611
15643
  if (!existsSync11(outputDir)) mkdirSync7(outputDir, { recursive: true });
15612
- const buildInputFilters = (ignoreKeyframes) => memberTracks.map((track, i) => {
15644
+ const buildInputFilters = (ignoreKeyframes, padMembers) => memberTracks.map((track, i) => {
15613
15645
  const volumeFilter = buildVolumeExpression(track, ignoreKeyframes);
15614
- return buildTrackInputFilter(track, i, volumeFilter, totalDuration);
15646
+ return buildTrackInputFilter(track, i, volumeFilter, totalDuration, padMembers);
15615
15647
  });
15616
15648
  const mixInputs = memberTracks.map((_, i) => `[a${i}]`).join("");
15617
15649
  const runOnce = async (useNormalize2, ignoreKeyframes = false) => {
15618
- const inputFilters = buildInputFilters(ignoreKeyframes);
15619
- const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0[out]` : (
15650
+ const inputFilters = buildInputFilters(ignoreKeyframes, !useNormalize2);
15651
+ const padUnitySumOnceToFilm = `[mixed];[mixed]apad,asetpts=N/SR/TB,atrim=0:${formatFilterNumber(totalDuration)}`;
15652
+ const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0${padUnitySumOnceToFilm}[out]` : (
15620
15653
  // Every member is padded and trimmed to totalDuration above, and
15621
15654
  // dropout_transition=0 keeps all N inputs active for that whole span.
15622
15655
  // amix's default normalize therefore divides by exactly N throughout;
@@ -15685,11 +15718,105 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
15685
15718
  }
15686
15719
  }
15687
15720
  if (signal?.aborted || !result.success) {
15688
- const failure = ffmpegFailure("mix", result);
15721
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15689
15722
  return { success: false, error: failure.detail, failure };
15690
15723
  }
15691
15724
  return { success: true, degradedAutomation };
15692
15725
  }
15726
+ var MAX_MIX_INPUTS = 96;
15727
+ async function sumInSlices(tracks, scratchDir, totalDuration, signal, config) {
15728
+ const wavTrack = (id, srcPath) => ({
15729
+ id,
15730
+ srcPath,
15731
+ start: 0,
15732
+ end: totalDuration,
15733
+ mediaStart: 0,
15734
+ duration: totalDuration,
15735
+ volume: 1
15736
+ });
15737
+ let running = null;
15738
+ let degradedAutomation = false;
15739
+ for (let at = 0; at < tracks.length; at += MAX_MIX_INPUTS) {
15740
+ const id = `sum-${at / MAX_MIX_INPUTS}`;
15741
+ const slicePath = join14(scratchDir, `${id}-slice.wav`);
15742
+ const slice = await mixGroupMembersFlat(
15743
+ tracks.slice(at, at + MAX_MIX_INPUTS),
15744
+ slicePath,
15745
+ totalDuration,
15746
+ signal,
15747
+ config
15748
+ );
15749
+ if (!slice.success) return slice;
15750
+ degradedAutomation ||= slice.degradedAutomation === true;
15751
+ if (!running) {
15752
+ running = wavTrack(id, slicePath);
15753
+ continue;
15754
+ }
15755
+ const foldedPath = join14(scratchDir, `${id}.wav`);
15756
+ const folded = await mixGroupMembersFlat(
15757
+ [running, wavTrack(`${id}-slice`, slicePath)],
15758
+ foldedPath,
15759
+ totalDuration,
15760
+ signal,
15761
+ config
15762
+ );
15763
+ rmSync8(running.srcPath, { force: true });
15764
+ rmSync8(slicePath, { force: true });
15765
+ if (!folded.success) return folded;
15766
+ running = wavTrack(id, foldedPath);
15767
+ }
15768
+ if (!running) throw new Error("[audioMixer] sumInSlices needs at least one track");
15769
+ return { success: true, track: running, degradedAutomation };
15770
+ }
15771
+ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal, config) {
15772
+ if (memberTracks.length <= MAX_MIX_INPUTS) {
15773
+ return mixGroupMembersFlat(memberTracks, outputPath, totalDuration, signal, config);
15774
+ }
15775
+ mkdirSync7(dirname7(outputPath), { recursive: true });
15776
+ const scratchDir = mkdtempSync7(join14(dirname7(outputPath), ".mix-batches-"));
15777
+ try {
15778
+ const summed = await sumInSlices(memberTracks, scratchDir, totalDuration, signal, config);
15779
+ if (!summed.success) return summed;
15780
+ renameSync3(summed.track.srcPath, outputPath);
15781
+ return { success: true, degradedAutomation: summed.degradedAutomation };
15782
+ } finally {
15783
+ rmSync8(scratchDir, { recursive: true, force: true });
15784
+ }
15785
+ }
15786
+ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config) {
15787
+ if (tracks.length <= MAX_MIX_INPUTS) {
15788
+ return mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, config);
15789
+ }
15790
+ mkdirSync7(dirname7(outputPath), { recursive: true });
15791
+ const scratchDir = mkdtempSync7(join14(dirname7(outputPath), ".mix-batches-"));
15792
+ try {
15793
+ const summed = await sumInSlices(tracks, scratchDir, totalDuration, signal, config);
15794
+ if (!summed.success) {
15795
+ return {
15796
+ success: false,
15797
+ outputPath,
15798
+ durationMs: 0,
15799
+ tracksProcessed: 0,
15800
+ error: summed.error,
15801
+ failures: [summed.failure]
15802
+ };
15803
+ }
15804
+ const mixed = await mixAudioTracksFlat(
15805
+ [summed.track],
15806
+ outputPath,
15807
+ totalDuration,
15808
+ signal,
15809
+ config
15810
+ );
15811
+ return mixed.success ? {
15812
+ ...mixed,
15813
+ tracksProcessed: tracks.length,
15814
+ error: summed.degradedAutomation ? "Volume automation exceeded this ffmpeg build's expression limits; rendered at base volume" : mixed.error
15815
+ } : mixed;
15816
+ } finally {
15817
+ rmSync8(scratchDir, { recursive: true, force: true });
15818
+ }
15819
+ }
15693
15820
  async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, totalDuration, signal, config) {
15694
15821
  const sumPath = join14(workDir, "master-sum.wav");
15695
15822
  const sum = await mixAudioTracks(tracks, sumPath, totalDuration, signal, config);
@@ -15703,7 +15830,11 @@ async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, tota
15703
15830
  timeout: config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15704
15831
  });
15705
15832
  if (!encoded.success) {
15706
- const failure = ffmpegFailure("mix", encoded);
15833
+ const failure = ffmpegFailure(
15834
+ "mix",
15835
+ encoded,
15836
+ config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15837
+ );
15707
15838
  return {
15708
15839
  success: false,
15709
15840
  outputPath,
@@ -15868,11 +15999,17 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15868
15999
  }
15869
16000
  audioSrcPath = trimmedPath;
15870
16001
  }
15871
- const automation = element.automation ? resolveAutomation(
15872
- parseAutomation(element.automation),
15873
- element.fxChain ? parseAudioFxChain(element.fxChain) : void 0
16002
+ const chain = element.fxChain ? parseAudioFxChain(element.fxChain) : void 0;
16003
+ const automation = element.automation ? resolveAutomation(parseAutomation(element.automation), chain) : null;
16004
+ const gainBeforeFx = chain !== void 0 && enabledAudioFxNodes(chain).length > 0;
16005
+ const laneKeyframes = automation ? volumeLaneKeyframes(
16006
+ automation,
16007
+ element.start,
16008
+ element.end - element.start,
16009
+ element.volume ?? 1,
16010
+ gainBeforeFx
15874
16011
  ) : null;
15875
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, element.start, element.end - element.start) : null;
16012
+ const tweened = !laneKeyframes && (element.volumeKeyframes?.length ?? 0) > 0;
15876
16013
  const envelopeKeyframes = laneKeyframes ?? element.volumeKeyframes;
15877
16014
  const envelope = envelopeKeyframes && envelopeKeyframes.length > 0 ? {
15878
16015
  keyframes: envelopeKeyframes,
@@ -15881,8 +16018,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15881
16018
  } : null;
15882
16019
  let bakedEnvelope = false;
15883
16020
  let tailSeconds = 0;
15884
- if (element.fxChain) {
15885
- const chain = parseAudioFxChain(element.fxChain);
16021
+ if (chain) {
15886
16022
  tailSeconds = chainTailSeconds(chain, automation ?? void 0);
15887
16023
  const fxResult = await applyAudioFxChain(
15888
16024
  audioSrcPath,
@@ -15892,7 +16028,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15892
16028
  trackId: element.id,
15893
16029
  signal: effectiveSignal,
15894
16030
  ...automation ? { automation } : {},
15895
- ...envelope ? { envelope } : {}
16031
+ ...envelope ? { envelope } : {},
16032
+ ...gainBeforeFx ? { inputGain: clipGain(element.volume ?? 1, tweened) } : {}
15896
16033
  }
15897
16034
  );
15898
16035
  audioSrcPath = fxResult.path;
@@ -15914,7 +16051,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15914
16051
  mediaStart: element.mediaStart,
15915
16052
  duration: element.end - element.start,
15916
16053
  // Gain is already in the samples when baked, so mix at unity.
15917
- volume: bakedEnvelope ? 1 : element.volume ?? 1,
16054
+ volume: bakedEnvelope || gainBeforeFx && !tweened ? 1 : element.volume ?? 1,
15918
16055
  volumeKeyframes: bakedEnvelope ? void 0 : envelopeKeyframes ?? void 0,
15919
16056
  ...element.fadeIn ? { fadeIn: element.fadeIn } : {},
15920
16057
  ...element.fadeOut ? { fadeOut: element.fadeOut } : {},
@@ -16031,7 +16168,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
16031
16168
  parseAutomation(meta.automation),
16032
16169
  meta.fxChain ? parseAudioFxChain(meta.fxChain) : void 0
16033
16170
  ) : null;
16034
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration) : null;
16171
+ const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration, meta.volume) : null;
16035
16172
  const envelope = laneKeyframes && laneKeyframes.length > 0 ? { keyframes: laneKeyframes, trackStart: 0, baseVolume: meta.volume } : null;
16036
16173
  let groupSrcPath = groupWavPath;
16037
16174
  let bakedEnvelope = false;
@@ -17316,6 +17453,7 @@ export {
17316
17453
  assertConfiguredFfmpegBinariesExist,
17317
17454
  cloneCaptureWarning,
17318
17455
  cloneCaptureWarnings,
17456
+ installMediaRenderIdBridge,
17319
17457
  createChromeMemorySampler,
17320
17458
  CaptureFailure,
17321
17459
  classifyCaptureFailure,
@@ -17344,6 +17482,7 @@ export {
17344
17482
  getCompositionDuration,
17345
17483
  percentileOf,
17346
17484
  getCapturePerfSummary,
17485
+ frameSequencePath,
17347
17486
  ManagedChildProcess,
17348
17487
  trackChildProcess,
17349
17488
  killTrackedProcesses,
@@ -17367,6 +17506,7 @@ export {
17367
17506
  extractVideoMetadata,
17368
17507
  extractAudioMetadata,
17369
17508
  analyzeKeyframeIntervals,
17509
+ appendSoftwareEncoderThreadsArg,
17370
17510
  PROVENANCE_RENDERER_TAG,
17371
17511
  PROVENANCE_VERSION_TAG,
17372
17512
  PROVENANCE_RENDERER_NAME,