hyperframes 0.8.186 → 0.8.188

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/dist/{add-UGK7AZI6.js → add-UORF6235.js} +12 -12
  2. package/dist/{animatedGifPrep-HPZPSB43.js → animatedGifPrep-23MLSEXF.js} +7 -7
  3. package/dist/api.js +15 -15
  4. package/dist/{asset-X2GTFWOY.js → asset-DOKDVHCW.js} +2 -2
  5. package/dist/{assetCataloger-5NWEG4IU.js → assetCataloger-CD27MHEY.js} +7 -7
  6. package/dist/{audio-7PBM5F3H.js → audio-3NMZHNNU.js} +15 -15
  7. package/dist/{auth-WABSMO46.js → auth-AWKTSKGC.js} +1 -1
  8. package/dist/{autoUpdate-6SCMLTSC.js → autoUpdate-VBOI4CNU.js} +2 -2
  9. package/dist/{backgroundChecks-XOGPAAR3.js → backgroundChecks-S4R454YU.js} +3 -3
  10. package/dist/backgroundChecksWorker.js +3 -3
  11. package/dist/{batchRender-VR6IA77K.js → batchRender-EGPYNDHK.js} +4 -4
  12. package/dist/{beats-3IWXLMSC.js → beats-YENEQUOA.js} +4 -4
  13. package/dist/{benchmark-2SSYATCX.js → benchmark-THSJIQG7.js} +9 -9
  14. package/dist/{browser-PMRN345D.js → browser-BDMVQ6WJ.js} +3 -3
  15. package/dist/{bundleWithLocalizedFonts-TT32MZBO.js → bundleWithLocalizedFonts-3QG722YD.js} +2 -2
  16. package/dist/{capture-BDGYTE57.js → capture-WM4XDMFX.js} +2 -2
  17. package/dist/{capture-4VD6AKUJ.js → capture-Z6BGCTK3.js} +10 -10
  18. package/dist/{captureCompositionFrame-5FVLAETA.js → captureCompositionFrame-EMHSAIBZ.js} +11 -11
  19. package/dist/{catalog-HUYZO5D7.js → catalog-7TV6UNCG.js} +12 -12
  20. package/dist/{catch-up-SSGTR2ZT.js → catch-up-63IUDZRW.js} +4 -4
  21. package/dist/{check-JWUWHGM2.js → check-7RHZL4DV.js} +32 -32
  22. package/dist/{checkBrowser-X7G6YNR7.js → checkBrowser-PF56CT5X.js} +29 -29
  23. package/dist/{chunk-5ZMXGNGC.js → chunk-2DHCCBCN.js} +9 -9
  24. package/dist/{chunk-ZKBW7NU2.js → chunk-2WUDFZCR.js} +2 -2
  25. package/dist/{chunk-YNH6ERDL.js → chunk-44QWRPGE.js} +2 -2
  26. package/dist/{chunk-ACLABKEF.js → chunk-46VJ2UAV.js} +5 -5
  27. package/dist/{chunk-4AFNEKU7.js → chunk-57XL23DE.js} +1 -1
  28. package/dist/{chunk-J36JVBLR.js → chunk-5C7R55BJ.js} +1 -1
  29. package/dist/{chunk-WHMFW5ET.js → chunk-5DPV7LU3.js} +3 -3
  30. package/dist/{chunk-YQICCAVA.js → chunk-5OZI55AC.js} +1 -1
  31. package/dist/{chunk-5UBZPG74.js → chunk-6A45RKAK.js} +1 -1
  32. package/dist/{chunk-LDW3KVGK.js → chunk-6HYPEIH7.js} +2 -2
  33. package/dist/{chunk-2PZZMAME.js → chunk-6HZ7TSJJ.js} +13 -8
  34. package/dist/{chunk-ROWEDRVX.js → chunk-6OWNUXTU.js} +1 -1
  35. package/dist/{chunk-77QM6YRQ.js → chunk-6Q5CFUAP.js} +1 -1
  36. package/dist/{chunk-DTXDQ6OP.js → chunk-B2P4JRUJ.js} +1 -1
  37. package/dist/chunk-BPX7LZG6.js +3896 -0
  38. package/dist/{chunk-TH7J3JZ5.js → chunk-BU4HRISA.js} +15 -15
  39. package/dist/{chunk-UJZT6HNN.js → chunk-BWEZDN6U.js} +1 -1
  40. package/dist/{chunk-MY6CVBDD.js → chunk-CE7EQUBV.js} +1 -1
  41. package/dist/{chunk-TQ2RRB5V.js → chunk-DA4TQFSF.js} +1 -1
  42. package/dist/{chunk-RPUIMFHF.js → chunk-DG4ME554.js} +1 -1
  43. package/dist/{chunk-Y2M6XSNK.js → chunk-DLFAVRF3.js} +1 -1
  44. package/dist/{chunk-TTVHYD5R.js → chunk-EI7FP7F3.js} +1 -1
  45. package/dist/{chunk-22QKQDEE.js → chunk-FOLMMQF3.js} +11 -11
  46. package/dist/{chunk-HZ7M2MZA.js → chunk-FSTLXRIN.js} +1 -1
  47. package/dist/{chunk-4NTW45W5.js → chunk-FWVILDOZ.js} +3 -3
  48. package/dist/{chunk-FCWFV4P7.js → chunk-GYBPAKBM.js} +2269 -2270
  49. package/dist/{chunk-ID5AJ4L3.js → chunk-HKUZCSNZ.js} +2 -2
  50. package/dist/{chunk-YRAJDFIN.js → chunk-HPQSUPR7.js} +1 -1
  51. package/dist/{chunk-3YHGQJI6.js → chunk-I7C5GUEQ.js} +3 -3
  52. package/dist/{chunk-7MVODWQF.js → chunk-IA2IDJAG.js} +1 -1
  53. package/dist/{chunk-PEIV2SOG.js → chunk-IJYR4TTA.js} +1 -1
  54. package/dist/{chunk-GTMBII7X.js → chunk-JWRT2IXO.js} +1 -1
  55. package/dist/{chunk-2II34JPM.js → chunk-JYC53CW4.js} +11 -11
  56. package/dist/{chunk-U4BMRIE6.js → chunk-KDBPZOXK.js} +9 -7
  57. package/dist/{chunk-FRLTKTOR.js → chunk-KWSEIVR6.js} +1 -1
  58. package/dist/{chunk-27H2363S.js → chunk-KY7375C4.js} +1 -1
  59. package/dist/{chunk-KNLSYCI5.js → chunk-L243NE72.js} +7 -7
  60. package/dist/{chunk-R6IGQ5NO.js → chunk-LWY3PFJ7.js} +2 -2
  61. package/dist/{chunk-5ZMIVTHN.js → chunk-LWYZN7HS.js} +1 -1
  62. package/dist/{chunk-DQZMJS2P.js → chunk-MJKUCR3C.js} +1 -1
  63. package/dist/{chunk-7AUMTARQ.js → chunk-MTPPEEYZ.js} +3 -3
  64. package/dist/{chunk-UAJCTEK2.js → chunk-MXXX6WVC.js} +1 -1
  65. package/dist/{chunk-Z5233OAN.js → chunk-NFHRGHCJ.js} +5 -5
  66. package/dist/{chunk-T4RP55P2.js → chunk-NFLZNSUZ.js} +1 -1
  67. package/dist/{chunk-4XFFJAWS.js → chunk-NMOTNNP2.js} +1 -1
  68. package/dist/{chunk-WLHNIIXU.js → chunk-OJOZXWLC.js} +3 -3
  69. package/dist/{chunk-HOFX63F2.js → chunk-OKSYDQHY.js} +3 -3
  70. package/dist/{chunk-6IOBAOU7.js → chunk-PMXR3IUH.js} +1 -1
  71. package/dist/{chunk-RFJ6SX4U.js → chunk-PQLFJYGP.js} +5 -5
  72. package/dist/{chunk-PFD25NAC.js → chunk-RIJHDTM5.js} +4 -0
  73. package/dist/{chunk-BRTDLVK3.js → chunk-SMF6YAUL.js} +9 -11
  74. package/dist/{chunk-RVZNSKRH.js → chunk-T3OXKSRG.js} +2 -2
  75. package/dist/{chunk-Q447CX26.js → chunk-TSFA2Y5C.js} +60 -47
  76. package/dist/{chunk-NPDPDEPP.js → chunk-UF6RCYA5.js} +1 -1
  77. package/dist/{chunk-EZINX6HH.js → chunk-V6NM4KL3.js} +3 -3
  78. package/dist/{chunk-U7QTMRLW.js → chunk-W2KGJHUL.js} +2 -2
  79. package/dist/{chunk-7MLFYMFB.js → chunk-X556BGL7.js} +1 -1
  80. package/dist/{chunk-UJKQWGFF.js → chunk-X7HBSTCZ.js} +8 -0
  81. package/dist/{chunk-CML25PAF.js → chunk-XNFDDVJS.js} +27 -3
  82. package/dist/{chunk-ZVBEGBII.js → chunk-XPVN4R66.js} +3 -3
  83. package/dist/{chunk-7M7TMM4S.js → chunk-YCW345M4.js} +147 -118
  84. package/dist/{chunk-RNZEQTWZ.js → chunk-YV4WT7CJ.js} +4 -2
  85. package/dist/{chunk-K6KBOORN.js → chunk-YZ73IPW2.js} +1 -1
  86. package/dist/{clean-IM2JIAUT.js → clean-43O47OTN.js} +16 -16
  87. package/dist/cli.js +53 -53
  88. package/dist/{cloud-6IPBCJSX.js → cloud-ZFWKFFSU.js} +4 -4
  89. package/dist/{cloudrun-MWZ6S2I2.js → cloudrun-XBBESKEU.js} +5 -5
  90. package/dist/{compare-WD7A2TV5.js → compare-OW4ELTFZ.js} +26 -26
  91. package/dist/{compiler-2EKSMC3I.js → compiler-23MT6CX6.js} +4 -4
  92. package/dist/{component-JKALWH36.js → component-SDV7LWH6.js} +3 -3
  93. package/dist/{compositions-643NAG4A.js → compositions-DI5QZCEK.js} +11 -11
  94. package/dist/{delete-EASPXKHQ.js → delete-QY2NSQ2G.js} +4 -4
  95. package/dist/deploy-MJVC4EFY.js +300 -0
  96. package/dist/{destroy-4B5HW5FH.js → destroy-SSPAKADI.js} +13 -3
  97. package/dist/{dist-LM7AVHOU.js → dist-JH32CBQL.js} +1 -1
  98. package/dist/{dist-F6FOKRSU.js → dist-RFTGYT26.js} +3 -3
  99. package/dist/{doctor-7XUVYDJQ.js → doctor-7ZOECPHR.js} +11 -11
  100. package/dist/{events-TULV6D3G.js → events-WIX4JM2K.js} +3 -3
  101. package/dist/{events-5TTPYBKG.js → events-XVQQGECC.js} +2 -2
  102. package/dist/{feedback-4FTPWM6P.js → feedback-GBYBR6UO.js} +7 -7
  103. package/dist/{ffmpeg-MP7ZVYLZ.js → ffmpeg-QGQBYMO3.js} +7 -7
  104. package/dist/{figma-B7LFWYKO.js → figma-2CMKUGJQ.js} +3 -3
  105. package/dist/fontLocalizeCli.js +9 -9
  106. package/dist/{get-F7W4AQ2V.js → get-RWCNUOHU.js} +4 -4
  107. package/dist/{gpuPolicy-NSNVDFXC.js → gpuPolicy-UGWSN32D.js} +7 -7
  108. package/dist/{grade-compare-6QXDTLS2.js → grade-compare-J2MX7HZL.js} +27 -27
  109. package/dist/{help-RJAA4O74.js → help-SH3ID7MV.js} +1 -1
  110. package/dist/{history-OOQBDVV5.js → history-C7V5QH7J.js} +6 -6
  111. package/dist/hyperframe.manifest.json +1 -1
  112. package/dist/hyperframe.runtime.iife.js +65 -64
  113. package/dist/hyperframes-player.global.js +3 -3
  114. package/dist/{info-5OGGYYLO.js → info-ZN3JJ3IN.js} +8 -8
  115. package/dist/{init-DM3TJKW2.js → init-KRNGSRNH.js} +27 -27
  116. package/dist/{inspect-FECQCLS5.js → inspect-AYDZXTX7.js} +27 -27
  117. package/dist/{installer-WOG4VORV.js → installer-N6AIK6XQ.js} +5 -5
  118. package/dist/{keyframes-SZHWUF5Y.js → keyframes-2RFUCEDK.js} +6 -6
  119. package/dist/{lambda-JG2ZRGMC.js → lambda-VW3CF6EE.js} +20 -13
  120. package/dist/{layout-7ACF7ZKC.js → layout-OE64APE7.js} +27 -27
  121. package/dist/{lint-ZCQGMILV.js → lint-UXJTX5WS.js} +7 -7
  122. package/dist/{lintProject-JATQKR64.js → lintProject-XM3DLUKG.js} +1 -1
  123. package/dist/{list-MH2X3Y2T.js → list-KOL6JPZJ.js} +7 -7
  124. package/dist/{login-CK46WX5U.js → login-MAQXOPDG.js} +4 -4
  125. package/dist/{manager-GXTPNJVW.js → manager-3LZKRK7P.js} +8 -8
  126. package/dist/{media-treatment-EH2AT34Z.js → media-treatment-COLSXXQJ.js} +13 -13
  127. package/dist/{models-D5XP2UK6.js → models-XXWMGOD7.js} +11 -11
  128. package/dist/{motionShot-UR7D3SRQ.js → motionShot-RECBNTBZ.js} +14 -14
  129. package/dist/{normalize-audio-635JOR5W.js → normalize-audio-LKVGHZWR.js} +6 -6
  130. package/dist/{open-6WE5WFVM.js → open-UEQVSRKH.js} +12 -12
  131. package/dist/{parakeetRunner-STHZPG32.js → parakeetRunner-QVPCZ36U.js} +12 -12
  132. package/dist/{pipeline-LVEKRETU.js → pipeline-5VSMQBKW.js} +8 -8
  133. package/dist/{play-W6TDW4FE.js → play-MNEABYN4.js} +19 -19
  134. package/dist/{policies-XXNGXWHV.js → policies-MBWGLMWB.js} +17 -0
  135. package/dist/{preflight-LIMQXLR7.js → preflight-ONJ74DWQ.js} +8 -8
  136. package/dist/{present-RX5QX6UR.js → present-GQ24GTYX.js} +15 -15
  137. package/dist/{preview-KIWTWK63.js → preview-ALL7O4RG.js} +18 -18
  138. package/dist/{producer-TSNKSCUM.js → producer-43L2UDF6.js} +1 -1
  139. package/dist/{progress-NHJIHAAG.js → progress-PX3PVAIM.js} +5 -3
  140. package/dist/{projectConfig-NCP7JRH4.js → projectConfig-VJXUXK7U.js} +7 -7
  141. package/dist/{publication-5W5UZWGC.js → publication-L6E5NTFV.js} +3 -3
  142. package/dist/{publish-7WR4XJ6I.js → publish-LCLBJEPR.js} +9 -9
  143. package/dist/{remove-background-43JGBRWQ.js → remove-background-PDL4KPR4.js} +9 -9
  144. package/dist/{render-7T44HIM6.js → render-7HVKJ7T4.js} +9 -7
  145. package/dist/{render-456HCO6O.js → render-J33ZHR6I.js} +44 -28
  146. package/dist/{render-WHMLYIOI.js → render-OPCRLZVL.js} +31 -22
  147. package/dist/{render-batch-LURBJTBV.js → render-batch-ZHHIGXU3.js} +5 -5
  148. package/dist/renderSetupWorker.js +1 -1
  149. package/dist/{resolver-MBIW4VC4.js → resolver-KNNIMMBM.js} +5 -5
  150. package/dist/{sam-MC24SZHC.js → sam-FHJ7IL7T.js} +3 -1
  151. package/dist/{sherpa-5J2AALED.js → sherpa-GI62H3H5.js} +11 -11
  152. package/dist/{sites-MAVH57MO.js → sites-5PWHAMEG.js} +1 -1
  153. package/dist/skills/hyperframes/references/motion.md +1 -0
  154. package/dist/{skills-UHOXOGGF.js → skills-IUTD6VQO.js} +5 -5
  155. package/dist/{skillsUpdateCheck-PDZIFUJE.js → skillsUpdateCheck-XZE7MJG6.js} +3 -3
  156. package/dist/{snapshot-PFV7ALLH.js → snapshot-XEXTHLZP.js} +32 -32
  157. package/dist/{src-XK5YIR6L.js → src-6STRGCDC.js} +8 -8
  158. package/dist/{src-DCVSEODX.js → src-ZPIV4PGX.js} +10 -6
  159. package/dist/{state-ORRPOLMQ.js → state-JPNCCXAV.js} +3 -1
  160. package/dist/{staticProjectServer-4IE4G4JO.js → staticProjectServer-HJLT7MHE.js} +15 -15
  161. package/dist/studio/assets/{hyperframes-player-B_skzvYC.js → hyperframes-player-DwWIKKZG.js} +1 -1
  162. package/dist/studio/assets/{index-CgLbvCUo.js → index-Cjwz7_13.js} +1 -1
  163. package/dist/studio/assets/{index-Dn5FAk2e.js → index-M8lEpS0Q.js} +130 -130
  164. package/dist/studio/index.html +1 -1
  165. package/dist/{studio-api-JMWYU7KF.js → studio-api-OBHEB5ND.js} +9 -9
  166. package/dist/{studioServer-BWLSTGIG.js → studioServer-KRIZFYML.js} +45 -45
  167. package/dist/{telemetry-JCU3AV7W.js → telemetry-N5OOVBCP.js} +3 -3
  168. package/dist/templates/_shared/AGENTS.md +5 -0
  169. package/dist/templates/_shared/CLAUDE.md +5 -0
  170. package/dist/{timeline-BCIFQYOM.js → timeline-YUFH65OG.js} +17 -17
  171. package/dist/{tokens-7IXKALMQ.js → tokens-K4VYLSWJ.js} +2 -2
  172. package/dist/{transcribe-HW7HGJ4V.js → transcribe-CY4CVO54.js} +16 -16
  173. package/dist/{transcribe-ZUQD3B32.js → transcribe-GMQXKDYN.js} +10 -10
  174. package/dist/{updateCheck-Z6LNEK7B.js → updateCheck-2625RXK4.js} +2 -2
  175. package/dist/{upgrade-JYH72JGL.js → upgrade-6ZY5MSBQ.js} +2 -2
  176. package/dist/{urlDownloader-WKLFHSUH.js → urlDownloader-PVJWG7V3.js} +6 -6
  177. package/dist/{validate-QCC27YYI.js → validate-XSRHEE7E.js} +21 -21
  178. package/dist/{video-EZ7ONY6L.js → video-MBFHC54F.js} +7 -7
  179. package/dist/warmCheck.js +34 -34
  180. package/package.json +18 -2
  181. package/dist/chunk-UOZX5W5T.js +0 -3852
  182. 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-YV4WT7CJ.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";
@@ -8936,7 +8995,7 @@ async function analyzeKeyframeIntervalsUncached(filePath, videoStreamStartSecond
8936
8995
 
8937
8996
  // ../engine/src/utils/renderProvenance.ts
8938
8997
  import { readFileSync as readFileSync2 } from "fs";
8939
- import { dirname, join as join4 } from "path";
8998
+ import { dirname, join as join5 } from "path";
8940
8999
  import { fileURLToPath } from "url";
8941
9000
  var PROVENANCE_RENDERER_TAG = "hyperframes_renderer";
8942
9001
  var PROVENANCE_VERSION_TAG = "hyperframes_version";
@@ -8947,7 +9006,7 @@ var MAX_WALK_DEPTH = 12;
8947
9006
  function resolveOwnVersionFrom(startDir) {
8948
9007
  let dir = startDir;
8949
9008
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
8950
- const candidate = join4(dir, "package.json");
9009
+ const candidate = join5(dir, "package.json");
8951
9010
  let pkg;
8952
9011
  try {
8953
9012
  pkg = JSON.parse(readFileSync2(candidate, "utf8"));
@@ -9280,7 +9339,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9280
9339
  const startTime = Date.now();
9281
9340
  const outputDir = dirname2(outputPath);
9282
9341
  if (!existsSync5(outputDir)) mkdirSync2(outputDir, { recursive: true });
9283
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9342
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9284
9343
  const frameCount = files.length;
9285
9344
  if (frameCount === 0) {
9286
9345
  return {
@@ -9296,7 +9355,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9296
9355
  if (options.useGpu) {
9297
9356
  gpuEncoder = await getCachedGpuEncoder();
9298
9357
  }
9299
- const inputPath = join5(framesDir, framePattern);
9358
+ const inputPath = frameSequencePath(framesDir, framePattern);
9300
9359
  const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
9301
9360
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
9302
9361
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG.ffmpegEncodeTimeout;
@@ -9352,7 +9411,7 @@ function buildConcatArgs(concatListPath, outputPath) {
9352
9411
  var concatListSeq = 0;
9353
9412
  function writeConcatList(dir, inputPaths) {
9354
9413
  concatListSeq += 1;
9355
- const listPath = join5(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9414
+ const listPath = join6(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9356
9415
  const body = inputPaths.map((p) => `file '${p.replace(/'/g, "'\\''")}'`).join("\n");
9357
9416
  writeFileSync2(listPath, body, "utf-8");
9358
9417
  return listPath;
@@ -9382,7 +9441,7 @@ async function concatVideoFiles(inputPaths, outputPath, signal, config) {
9382
9441
  }
9383
9442
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal, config, onFramesEncoded) {
9384
9443
  const start = Date.now();
9385
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9444
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9386
9445
  if (files.length === 0) {
9387
9446
  return {
9388
9447
  success: false,
@@ -9396,7 +9455,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9396
9455
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
9397
9456
  const chunkCount = Math.ceil(files.length / chunkSize);
9398
9457
  mkdirSync2(dirname2(outputPath), { recursive: true });
9399
- const chunkDir = mkdtempSync(join5(dirname2(outputPath), "chunk-encode-"));
9458
+ const chunkDir = mkdtempSync(join6(dirname2(outputPath), "chunk-encode-"));
9400
9459
  const chunkPaths = [];
9401
9460
  for (let i = 0; i < chunkCount; i++) {
9402
9461
  if (signal?.aborted) {
@@ -9412,8 +9471,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9412
9471
  const startNumber = i * chunkSize;
9413
9472
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
9414
9473
  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);
9474
+ const chunkPath = join6(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
9475
+ const inputPath = frameSequencePath(framesDir, framePattern);
9417
9476
  const inputArgs = [
9418
9477
  "-framerate",
9419
9478
  fpsToFfmpegArg(options.fps),
@@ -9582,14 +9641,14 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9582
9641
  "-master_pl_name",
9583
9642
  HLS_MASTER_PLAYLIST,
9584
9643
  "-hls_segment_filename",
9585
- join5(outputDir, "%v_%05d.ts"),
9644
+ join6(outputDir, "%v_%05d.ts"),
9586
9645
  // Without these the mpegts muxer starts the stream at PTS 1.4 s.
9587
9646
  "-muxdelay",
9588
9647
  "0",
9589
9648
  "-muxpreload",
9590
9649
  "0"
9591
9650
  );
9592
- args.push("-y", join5(outputDir, "%v.m3u8"));
9651
+ args.push("-y", join6(outputDir, "%v.m3u8"));
9593
9652
  const processTimeout = options.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
9594
9653
  const result = await runFfmpeg(args, { signal, timeout: processTimeout });
9595
9654
  if (signal?.aborted) {
@@ -9601,7 +9660,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9601
9660
  };
9602
9661
  }
9603
9662
  if (result.success && hasAudio) {
9604
- const masterPath = join5(outputDir, HLS_MASTER_PLAYLIST);
9663
+ const masterPath = join6(outputDir, HLS_MASTER_PLAYLIST);
9605
9664
  let master;
9606
9665
  try {
9607
9666
  master = openSync(masterPath, "r+", 384);
@@ -10222,7 +10281,7 @@ function isKnownInactiveTimelineWindow(element, resolvedStart) {
10222
10281
  // ../engine/src/utils/alphaPlaneProbe.ts
10223
10282
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync } from "fs";
10224
10283
  import { tmpdir as tmpdir2 } from "os";
10225
- import { join as join6 } from "path";
10284
+ import { join as join7 } from "path";
10226
10285
  var BYTES_PER_SAMPLE_FRAME = 8 * 8 * 4;
10227
10286
  var MAX_SAMPLE_BYTES = BYTES_PER_SAMPLE_FRAME * 3;
10228
10287
  function sampledRgbaAlphaIsFullyOpaque(buf) {
@@ -10237,8 +10296,8 @@ function sampledRgbaAlphaIsFullyOpaque(buf) {
10237
10296
  async function probeInputAlphaPlane(videoPath, decoder, signal) {
10238
10297
  let probeDir;
10239
10298
  try {
10240
- probeDir = mkdtempSync2(join6(tmpdir2(), "hf-alpha-probe-"));
10241
- const samplePath = join6(probeDir, "alpha.raw");
10299
+ probeDir = mkdtempSync2(join7(tmpdir2(), "hf-alpha-probe-"));
10300
+ const samplePath = join7(probeDir, "alpha.raw");
10242
10301
  const result = await runFfmpeg(
10243
10302
  [
10244
10303
  "-v",
@@ -10284,7 +10343,7 @@ import {
10284
10343
  mkdirSync as mkdirSync4,
10285
10344
  lstatSync,
10286
10345
  openSync as openSync2,
10287
- readdirSync as readdirSync3,
10346
+ readdirSync as readdirSync4,
10288
10347
  rmdirSync,
10289
10348
  rmSync as rmSync2,
10290
10349
  statSync as statSync5,
@@ -10292,7 +10351,7 @@ import {
10292
10351
  } from "fs";
10293
10352
  import { createHash, randomUUID as randomUUID2 } from "crypto";
10294
10353
  import { BlockList, isIP } from "net";
10295
- import { dirname as dirname4, extname as extname2, join as join7 } from "path";
10354
+ import { dirname as dirname4, extname as extname2, join as join8 } from "path";
10296
10355
  import { Readable, Transform } from "stream";
10297
10356
  import { pipeline } from "stream/promises";
10298
10357
  var inFlightDownloads = /* @__PURE__ */ new Map();
@@ -10462,7 +10521,7 @@ function sameCacheLockStatGeneration(left, right) {
10462
10521
  function observeCachePathLock(lockPath) {
10463
10522
  for (let pass = 0; pass < 3; pass += 1) {
10464
10523
  const before = lstatSync(lockPath);
10465
- const owner = readdirSync3(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10524
+ const owner = readdirSync4(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10466
10525
  const after = lstatSync(lockPath);
10467
10526
  if (sameCacheLockStatGeneration(before, after)) return { stats: after, owner };
10468
10527
  }
@@ -10484,7 +10543,7 @@ function removeCacheLockDirectoryIfEmpty(lockPath) {
10484
10543
  }
10485
10544
  function releaseOwnedCachePathLock(lockPath, owner) {
10486
10545
  try {
10487
- rmdirSync(join7(lockPath, owner));
10546
+ rmdirSync(join8(lockPath, owner));
10488
10547
  } catch (error) {
10489
10548
  if (error.code === "ENOENT") return;
10490
10549
  throw error;
@@ -10525,12 +10584,12 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10525
10584
  if (createdLock) {
10526
10585
  const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
10527
10586
  try {
10528
- mkdirSync4(join7(lockPath, owner));
10529
- const entries = readdirSync3(lockPath);
10587
+ mkdirSync4(join8(lockPath, owner));
10588
+ const entries = readdirSync4(lockPath);
10530
10589
  if (entries.length === 1 && entries[0] === owner) {
10531
10590
  return () => releaseOwnedCachePathLock(lockPath, owner);
10532
10591
  }
10533
- rmdirSync(join7(lockPath, owner));
10592
+ rmdirSync(join8(lockPath, owner));
10534
10593
  removeCacheLockDirectoryIfEmpty(lockPath);
10535
10594
  continue;
10536
10595
  } catch (error) {
@@ -10548,7 +10607,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10548
10607
  if (Date.now() - observedLock.stats.mtimeMs > CACHE_LOCK_STALE_MS) {
10549
10608
  if (observedLock.owner) {
10550
10609
  try {
10551
- rmdirSync(join7(lockPath, observedLock.owner));
10610
+ rmdirSync(join8(lockPath, observedLock.owner));
10552
10611
  } catch (error) {
10553
10612
  if (error.code === "ENOENT") continue;
10554
10613
  throw error;
@@ -10556,7 +10615,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10556
10615
  removeCacheLockDirectoryIfEmpty(lockPath);
10557
10616
  continue;
10558
10617
  }
10559
- const reclaimPath = join7(lockPath, CACHE_LOCK_RECLAIM_NAME);
10618
+ const reclaimPath = join8(lockPath, CACHE_LOCK_RECLAIM_NAME);
10560
10619
  try {
10561
10620
  mkdirSync4(reclaimPath);
10562
10621
  } catch (error) {
@@ -10996,8 +11055,8 @@ function emitDownloadTelemetry(options, event) {
10996
11055
  }
10997
11056
  }
10998
11057
  async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, signal) {
10999
- const attemptDir = mkdtempSync3(join7(dirname4(localPath), ".hf-download-"));
11000
- const partialPath = join7(attemptDir, "payload");
11058
+ const attemptDir = mkdtempSync3(join8(dirname4(localPath), ".hf-download-"));
11059
+ const partialPath = join8(attemptDir, "payload");
11001
11060
  const controller = new AbortController();
11002
11061
  let timedOut = false;
11003
11062
  let callerAborted = signal?.aborted ?? false;
@@ -11233,7 +11292,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5, signal, onTransient
11233
11292
  mkdirSync4(destDir, { recursive: true });
11234
11293
  }
11235
11294
  const filename = getFilenameFromUrl(url, validationScope);
11236
- const localPath = join7(destDir, filename);
11295
+ const localPath = join8(destDir, filename);
11237
11296
  const downloadPromise = (async () => {
11238
11297
  const cacheStartedAt = Date.now();
11239
11298
  const reused = await reuseOrInvalidateCachedFile(
@@ -11317,56 +11376,6 @@ import {
11317
11376
  writeFileSync as writeFileSync3
11318
11377
  } from "fs";
11319
11378
  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
11379
  var COMPLETE_SENTINEL = ".hf-complete";
11371
11380
  var GC_MARKER = ".hf-last-gc";
11372
11381
  var SCHEMA_PREFIX = "hfcache-v8-";
@@ -12118,7 +12127,7 @@ async function extractVideoFramesRangeUnredacted(videoPath, videoId, startTime,
12118
12127
  }
12119
12128
  const format = resolveFrameFormat(metadata, options.format);
12120
12129
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
12121
- const outputPattern = join10(videoOutputDir, framePattern);
12130
+ const outputPattern = frameSequencePath(videoOutputDir, framePattern);
12122
12131
  const isHdr = isHdrColorSpace(metadata.colorSpace);
12123
12132
  const isMacOS = process.platform === "darwin";
12124
12133
  const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
@@ -14054,6 +14063,8 @@ async function encodeAac(input, outputPath, totalDuration, {
14054
14063
  segments.map(
14055
14064
  ({ trim, path }) => runFfmpeg(
14056
14065
  [
14066
+ "-cpuflags",
14067
+ "0",
14057
14068
  "-i",
14058
14069
  input,
14059
14070
  "-af",
@@ -14579,7 +14590,7 @@ import { join as join13 } from "path";
14579
14590
  import { pathToFileURL } from "url";
14580
14591
 
14581
14592
  // ../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';
14593
+ var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var ve="data-fx-chain",Pt=ve.slice(5),W=1,F=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),S=(e=.707,t="Bandwidth: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),N=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[N(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[F("frequency","Frequency",1e3),N(-40,40,0),S(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[F("frequency","Frequency",200,20,2e3),N(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),N(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[F("frequency","Cutoff",300,20,2e4),S(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[F("frequency","Cutoff",8e3,100,2e4),S(.707),K],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Peak limiter that follows the signal\'s level. It has no lookahead, so a fast peak or an inter-sample peak can pass the ceiling; use the true-peak limiter for a delivery limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"truepeak",label:"True-peak limiter",group:"dynamics",description:"Lookahead limiter that holds a 4x estimate of the inter-sample peak at a dBTP ceiling; full-band noise can land up to about 1.7 dB above it. Adds lookahead plus about 0.3 ms of latency.",params:[{kind:"number",key:"ceiling",label:"Ceiling",unit:"dBTP",min:-24,max:0,step:.1,default:-1,hint:"Target true peak, measured on a 4x oversampled signal. Dense full-band sound can end up to about 1.7 dB above it."},{kind:"number",key:"lookahead",label:"Lookahead",unit:"ms",min:.5,max:10,step:.1,default:3,hint:"How far ahead the gain starts falling. Longer is smoother and adds the same latency."},{kind:"number",key:"release",label:"Release",unit:"ms",min:10,max:2e3,step:1,default:80,scale:"log"}],web:"worklet-truepeak"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times, a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],C=new Map(z.map(e=>[e.id,e]));function P(e){return C.get(e)}var Ht=z.map(e=>e.id);function y(e,t){let n=C.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(u=>u.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var x=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Chain file must be a JSON object.");let n=t;if(n.version!==W)throw new x(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new x("Chain file is missing a `nodes` array.");let o=n.nodes.map(Me);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function Me(e,t){if(typeof e!="object"||e===null)throw new x(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!C.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Fe({type:n.type,id:E(n.id),fromCarve:Y(n.fromCarve),fromPreset:E(n.fromPreset),label:E(n.label),fromEq:E(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:Te(n.presetAmount),enabled:n.enabled!==!1,params:y(n.type,n.params??void 0)})}function E(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function Te(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function Fe(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Pe(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Pe(e,t)+16}var Z=`\nconst TP_OS = 4;\nconst TP_HALF = 64;\nconst TP_BRANCH = 33;\nconst TP_DELAY = 16;\n\nfunction besselI0(x) {\n let sum = 1, term = 1;\n for (let k = 1; k < 40; k++) {\n term *= (x / (2 * k)) * (x / (2 * k));\n sum += term;\n }\n return sum;\n}\n\n/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */\nfunction tpBranches() {\n const beta = 6;\n const norm = besselI0(beta);\n const branches = [];\n for (let p = 0; p < TP_OS; p++) {\n const taps = new Float64Array(TP_BRANCH);\n let sum = 0;\n for (let j = 0; j < TP_BRANCH; j++) {\n const k = p + TP_OS * j;\n if (k > 2 * TP_HALF) continue;\n const t = (k - TP_HALF) / TP_OS;\n const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);\n const r = (k - TP_HALF) / TP_HALF;\n taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);\n sum += taps[j];\n }\n // Unity gain at DC on every branch, so a steady level reads as itself.\n for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;\n branches.push(taps);\n }\n return branches;\n}\n\nclass HfTruePeak extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));\n this.delayLen = this.look + TP_DELAY;\n this.branches = tpBranches();\n this.setLevels();\n this.clear();\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n if (e.data && e.data.__hfReset) { this.clear(); return; }\n this.p = { ...this.p, ...e.data };\n this.setLevels();\n };\n }\n /** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */\n clear() {\n this.ch = [];\n this.rel = 1;\n this.dqIdx = new Int32Array(this.look + 2);\n this.dqVal = new Float64Array(this.look + 2);\n this.dqHead = 0;\n this.dqTail = 0;\n this.t = 0;\n this.avgRing = new Float64Array(this.look + 1).fill(1);\n this.avgPos = 0;\n this.avgSum = this.look + 1;\n }\n setLevels() {\n this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);\n this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));\n }\n channel(c) {\n if (!this.ch[c]) {\n this.ch[c] = {\n delay: new Float32Array(this.delayLen),\n w: 0,\n hist: new Float32Array(TP_BRANCH * 2),\n pos: 0,\n };\n }\n return this.ch[c];\n }\n /** Largest interpolated magnitude across channels for the newest sample. */\n peak(chans, inputs, n) {\n let peak = 0;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n const x = inputs[c][n];\n s.pos = (s.pos + 1) % TP_BRANCH;\n s.hist[s.pos] = x;\n s.hist[s.pos + TP_BRANCH] = x;\n const base = s.pos + TP_BRANCH;\n for (let p = 0; p < TP_OS; p++) {\n const taps = this.branches[p];\n let acc = 0;\n for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];\n const a = acc < 0 ? -acc : acc;\n if (a > peak) peak = a;\n }\n }\n return peak;\n }\n /** Windowed minimum of the required gain over the last look + 1 values. */\n windowMin(v) {\n const cap = this.dqIdx.length;\n while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {\n this.dqTail = (this.dqTail + cap - 1) % cap;\n }\n this.dqIdx[this.dqTail] = this.t;\n this.dqVal[this.dqTail] = v;\n this.dqTail = (this.dqTail + 1) % cap;\n while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {\n this.dqHead = (this.dqHead + 1) % cap;\n }\n this.t++;\n return this.dqVal[this.dqHead];\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const chans = i.length;\n const frames = i[0].length;\n for (let n = 0; n < frames; n++) {\n const peak = this.peak(chans, i, n);\n const need = peak > this.ceiling ? this.ceiling / peak : 1;\n const m = this.windowMin(need);\n this.avgSum += m - this.avgRing[this.avgPos];\n this.avgRing[this.avgPos] = m;\n this.avgPos = (this.avgPos + 1) % this.avgRing.length;\n const target = Math.min(1, this.avgSum / this.avgRing.length);\n const released = this.rel + (1 - this.rel) * this.relCoef;\n this.rel = released < target ? released : target;\n if (target >= 1 && this.rel > 0.9999999) this.rel = 1;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n o[c][n] = s.delay[s.w] * this.rel;\n s.delay[s.w] = i[c][n];\n s.w = (s.w + 1) % this.delayLen;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-truepeak", HfTruePeak);\n`;var He=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left, so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.start = Math.round((this.p.__hfElapsed ?? 0) * sampleRate);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n // Primed from the input: a counter seeded mid-hold would otherwise play 0 until it wraps.\n if (this.held[ch] === undefined) this.held[ch] = Math.round(inp[0] * levels) / levels;\n // The hold counter runs from the file\'s first sample, so a node built part-way in starts where it would be.\n if (this.holds[ch] === undefined) this.holds[ch] = this.start % step;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point, which hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel). Advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n // The taps sweep at a constant rate from the start of the file, so a node built\n // part-way in joins the sweep where it would already be.\n const ratio0 = Math.pow(2, Math.max(-12, Math.min(12, this.p.semitones ?? 0)) / 12);\n const turns = (this.p.__hfElapsed ?? 0) * sampleRate * ((1 - ratio0) / this.grain);\n this.phase = turns - Math.floor(turns);\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros, so the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0. A one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal, while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`+Z,L=new WeakMap;var _e=new WeakSet;function O(e){let t=L.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(He)))}`;await e.audioWorklet.addModule(n),_e.add(e)})().catch(n=>{throw L.delete(e),n}),L.set(e,t)),t}function Re(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),u=Math.max(.001,1-Math.max(0,Math.min(1,n))),l=0,m=0;for(let c=0;c<a;c++){l+=u*(s()-l);let b=l*Math.pow(1-c/a,2.5);r[c]=b,m+=b*b}let p=Math.sqrt(m);if(p>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/p;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function ae(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let l=0;l<a;l++){let m=l/a;i[l]=t==="sine"?Math.sin(2*Math.PI*m):4*Math.abs((m+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let u=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,u),s}function oe(e){try{e.stop()}catch{}e.disconnect()}function I(e,t){return[{param:e},{param:t,map:n=>1-n}]}function re(e,t,n){e.gain.value=n,t.gain.value=1-n}function q(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var Ne=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Ee=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Ne.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function De(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o,a)=>{let r=new AudioWorkletNode(n,e,{processorOptions:{...o,__hfElapsed:a}});return{input:r,output:r,update:i=>r.port.postMessage({...i}),...t?{reset:()=>r.port.postMessage({__hfReset:!0})}:{},dispose:()=>{r.port.postMessage({__hfDispose:!0}),r.disconnect()}}}}var Se={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},Ce=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=Se[String(i.type)]??Math.tanh,u=Math.pow(10,f(i.threshold)/20),l=8192,m=new Float32Array(l);for(let p=0;p<l;p++){let c=p/(l-1)*2-1;m[p]=s(c/Math.max(1e-6,u))*u}n.curve=m,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Le=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let u=l=>{a.delayTime.value=Math.min(5,f(l.time)/1e3),r.gain.value=f(l.feedback),re(i,s,f(l.mix))};return u(t),{input:n,output:o,update:u,automation:{time:[{param:a.delayTime,map:l=>Math.min(5,B(l))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(l=>l.disconnect())}},Oe=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),u=e.createGain(),l=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(u).connect(a),o.connect(l).connect(a);let m=p=>{r.delayTime.value=f(p.delay)/1e3,s.gain.value=f(p.depth)/1e3,i.playbackRate.value=f(p.speed),re(u,l,f(p.mix))};return m(t),{input:o,output:a,update:m,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(u.gain,l.gain)},dispose:()=>{oe(i),[o,a,r,s,u,l].forEach(p=>p.disconnect())}}},Be=6,Ie=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=ae(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),u=e.createGain(),l=e.createGain(),m=e.createGain(),p=[];o.connect(r);let c=r;for(let d=0;d<Be;d++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,u.connect(h.frequency),c.connect(h),c=h,p.push(h)}s.connect(u),c.connect(l).connect(i),r.connect(m).connect(i),i.connect(a);let b=d=>{let h=1e3/Math.max(.1,f(d.delay));for(let g of p)g.frequency.value=h;u.gain.value=h*f(d.decay),s.playbackRate.value=f(d.speed),r.gain.value=f(d.in_gain),i.gain.value=f(d.out_gain),l.gain.value=1,m.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{oe(s),[o,a,r,i,u,l,m,...p].forEach(d=>d.disconnect())}}},qe=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",u=l=>{let m=`${f(l.size)}:${f(l.damping)}`;if(m!==s){let p=Re(e.sampleRate,f(l.size),f(l.damping)),c=e.createBuffer(1,p.length,e.sampleRate);c.getChannelData(0).set(p),a.buffer=c,s=m}r.gain.value=f(l.wet),i.gain.value=f(l.dry)};return u(t),{input:n,output:o,update:u,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(l=>l.disconnect())}},Ge={"gain-node":Ee,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Le,"chorus-lfo":Oe,"allpass-phaser":Ie,convolver:qe};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ve(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return De(t)(e,r,o);let i=Ge[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var p;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),u=o;for(let c of s){let b=null;if(c.preset){let d=e.createGain(),h=e.createGain(),g=e.createGain(),T=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,u.connect(d),d.connect(h).connect(T),b={entry:d,wet:g,dry:h,join:T},u=d}for(let d of c.nodes){let h=Ve(e,d.type,d.params??{},n);u.connect(h.input),u=h.output,r.push({...d.id?{id:d.id}:{},type:d.type,handle:h})}b&&c.preset&&(u.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),u=b.join)}u.connect(a);let l=ne(t),m={};for(let c of i)(m[p=c.id]??(m[p]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:m,nodes:r,update(c){if(ne(c)!==l)return!1;w(c).forEach((d,h)=>{let g=r[h];g&&(g.handle.update(y(d.type,d.params)),d.id===void 0?delete g.id:g.id=d.id)});let b=0;for(let d of te(w(c))){if(!d.preset)continue;let h=i[b++];!h||h.id!==d.preset||(h.wet.gain.value=d.amount,h.dry.gain.value=1-d.amount)}return!0},reset(){for(let{handle:c}of r)c.reset?.()},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:d,join:h}of i)c.disconnect(),b.disconnect(),d.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,V=10;var $e="data-automation",Xt=$e.slice(5),D=1,je=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},U="volume",le="rate";function R(e){if(e===U)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===Xe){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var Xe="preset";var We={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1};function me(e){return!!e.curve||e.viaX!==void 0&&e.viaY!==void 0}var pe={min:G,max:V,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=R(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return pe;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...We,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function Ke(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function he(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ye(e){let t=Ke(e?.curve),n=he(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function ze(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ye(e)}}function fe(e,t){let n=e.map(a=>ze(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,je)}function Je(e){let t=[];for(let n of e.lanes){if(!R(n.target))continue;let o=n.target===U?de:n.target===le?pe:null,a=fe(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function be(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=fe(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function ge(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!R(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return Je({version:D,lanes:o})}function Qe(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Ze(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,u=Math.min(r,Math.max(1,i,s));return{cx:e+n/u,cy:t+o/u,w:u}}function et(e,t,n){let{cx:o,cy:a,w:r}=Ze(t,n),i=2-2*r,s=e*i-1+2*r*o,u=-e*i-2*r*o,l=tt(s,u,e),m=1-l,p=m*m+2*r*l*m+l*l;return p>0?(2*r*a*l*m+l*l)/p:e}function tt(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function nt(e,t){let n=he(t.viaX,t.viaY);return n?et(Math.min(1,Math.max(0,e)),n.x,n.y):Qe(e,t.curve)}function at(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function j(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let p=i+s>>1;o[p].t<=t?i=p:s=p}let u=o[i],l=o[s],m=l.t-u.t;return m<=0?l.v:at(u.v,l.v,nt((t-u.t)/m,u),n)}function Ae(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function xe(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let u=0;u<r;u+=1)i[u]=j(e,t+s*u/(r-1),a);return i}var ot=100,rt=8,it=4096,X=e=>e;function st(e,t,n){return!me(e)&&t==="linear"&&!n}function ut(e){let t=Math.ceil(e*ot);return Math.min(it,Math.max(rt,t))}function lt(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,u=a??X,l=v=>r+(v-i)/s,m=e.points[t],p=e.points[t+1],c=l(p.t);if(c<=r)return null;let b=Math.max(r,l(m.t)),d=c-b;if(d<=0)return null;if(st(m,n,a))return{kind:"ramp",time:c,value:u(p.v)};let h=ut(d),g=xe(e,Math.max(m.t,i),p.t,h,n),T=new Float32Array(h);for(let v=0;v<h;v+=1)T[v]=u(g[v]??0);return{kind:"curve",time:b,duration:d,values:T}}function ct(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=lt(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??X)(j(e,n.elapsed,t))}),a}function dt(e,t){e.cancelScheduledValues(0);for(let n of t)n.kind==="set"?e.setValueAtTime(n.value,n.time):n.kind==="ramp"?e.linearRampToValueAtTime(n.value,n.time):(e.setValueAtTime(n.values[0]??0,n.time),e.linearRampToValueAtTime(n.values[n.values.length-1]??0,n.time+n.duration))}function mt(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{dt(e,t);return}}function pt(e){for(let t of e)t.param.cancelScheduledValues(0)}function ht(e,t,n,o){if(t.points.length!==0)for(let a of e){if(pt([a]),Ae(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}mt(a.param,ct(t,n,o,a.map))}}function ye(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let u=R(s.target);if(!u)continue;let l=u.kind==="preset"?a?.[u.presetId]:u.kind==="fx"?r.get(u.nodeId)?.automation?.[u.param]:void 0;if(!l||l.length===0)continue;let m=$(s.target,t);m&&(ht(l,s,m.scale,o),i.push(...l))}return i}var ft=5,bt=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function gt(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(bt)/Math.log(o))*n}function At(e){return .6+Math.max(0,Math.min(1,e))*2.6}function xt(e,t){return k(e,"wet",t)>0?At(k(e,"size",t)):0}function yt(e,t){return k(e,"mix",t)>0?gt(k(e,"time",t),k(e,"feedback",t)):0}function kt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function wt(e,t){return k(e,"mix",t)>0?.2:0}function vt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return xt(e,t);case"delay":return yt(e,t);case"chorus":return kt(e,t);case"pitchshift":return wt(e,t);default:return 0}}function ke(e,t){let n=e.nodes.reduce((o,a)=>o+vt(a,t),0);return Math.min(ft,n)}function we(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function Mt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function Tt(e,t){let n=[];for(let o=0;o<e.numberOfChannels;o++)n.push(new Float32Array(e.getChannelData(o).subarray(t)));return n}async function Ft(e,t,n,o,a=0){let r=J(n),i=Math.max(1,e.length),s=e[0]?.length??0;if(s===0)return e;let u=o?be(ge(o),r):null,l=Math.ceil(ke(r,u??void 0)*t),m=we(r,t),p=s+l+m,c=new OfflineAudioContext(i,p,t);ie(r)&&await O(c);let b=c.createBufferSource();b.buffer=Mt(c,e,i,p,t);let d=se(c,r,a);return u&&ye(u,r,d.nodes,{scheduledAt:0,elapsed:0,rate:1},d.presets),b.connect(d.input),d.output.connect(c.destination),b.start(),Tt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Ft};})();\n';
14583
14594
  function getAudioFxRuntimeScript() {
14584
14595
  return AUDIO_FX_RUNTIME_IIFE;
14585
14596
  }
@@ -14765,6 +14776,9 @@ function applyEnvelopeToPlanes(planes, sampleRate, gainAt, startFrame = 0) {
14765
14776
  for (const plane of planes) plane[frame] = (plane[frame] ?? 0) * gain;
14766
14777
  }
14767
14778
  }
14779
+ function applyInputGain(planes, gain = 1) {
14780
+ if (gain !== 1) applyEnvelopeToPlanes(planes, 1, () => gain);
14781
+ }
14768
14782
  var DEFAULT_STREAMING_LIMITS = {
14769
14783
  aboveBytes: 512 * 1024 * 1024,
14770
14784
  segmentSeconds: 300,
@@ -14838,6 +14852,7 @@ async function renderInSegments(openPage, inputWav, header, chain, outputWav, op
14838
14852
  throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
14839
14853
  }
14840
14854
  const planes = deinterleave(readFrames(inFd, header, seg.readFrom, seg.readTo), channels);
14855
+ applyInputGain(planes, options.inputGain);
14841
14856
  const page = await openPage();
14842
14857
  let outPlanes;
14843
14858
  try {
@@ -14897,6 +14912,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
14897
14912
  }
14898
14913
  const input = readWav(inputWav);
14899
14914
  const planes = deinterleave(input.samples, input.channels);
14915
+ applyInputGain(planes, options.inputGain);
14900
14916
  if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
14901
14917
  return withFxBrowser(options, async (openPage) => {
14902
14918
  const page = await openPage();
@@ -15256,7 +15272,7 @@ function downloadFailure(error, elementId) {
15256
15272
  detail: boundedDetail(`Download failed for audio element ${elementId}: ${message}`)
15257
15273
  };
15258
15274
  }
15259
- function ffmpegFailure(stage, result, elementId) {
15275
+ function ffmpegFailure(stage, result, timeoutMs, elementId) {
15260
15276
  const stderr = result.stderr ?? "";
15261
15277
  let reason = "ffmpeg_failed";
15262
15278
  let owner = "system";
@@ -15287,32 +15303,32 @@ function ffmpegFailure(stage, result, elementId) {
15287
15303
  elementId,
15288
15304
  subprocess: audioSubprocessEvidence(result),
15289
15305
  detail: boundedDetail(
15290
- result.error?.message ? `${formatFfmpegError(result.exitCode, stderr)}: ${result.error.message}` : formatFfmpegError(result.exitCode, stderr)
15306
+ result.error?.message ? `${describeFfmpegFailure(result, timeoutMs)}: ${result.error.message}` : describeFfmpegFailure(result, timeoutMs)
15291
15307
  )
15292
15308
  };
15293
15309
  }
15294
- var CURVED_SEGMENT_SAMPLES_PER_SEC = 30;
15295
- function volumeLaneKeyframes(automation, trackStart, duration) {
15310
+ function volumeLaneKeyframes(automation, trackStart, duration, gain, gainAhead = false) {
15296
15311
  const lane = automation.lanes.find((l) => l.target === VOLUME_TARGET);
15297
15312
  if (!lane || lane.points.length === 0) return null;
15313
+ const points = gainedVolumePoints(lane, gain, gainAhead);
15298
15314
  const out = [];
15299
15315
  const push = (t, v) => {
15300
15316
  out.push({ time: trackStart + t, volume: v });
15301
15317
  };
15302
- const first = lane.points[0];
15318
+ const first = points[0];
15303
15319
  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];
15320
+ for (let i = 0; i < points.length; i += 1) {
15321
+ const a = points[i];
15306
15322
  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));
15323
+ const b = points[i + 1];
15324
+ if (!b || !isBentSegment(a)) continue;
15325
+ const steps = volumeSegmentSampleCount(b.t - a.t);
15310
15326
  for (let k = 1; k < steps; k += 1) {
15311
15327
  const t = a.t + (b.t - a.t) * k / steps;
15312
- push(t, sampleAutomationLane(lane, t));
15328
+ push(t, gainedVolume(sampleAutomationLane(lane, t), gain, gainAhead));
15313
15329
  }
15314
15330
  }
15315
- const last = lane.points[lane.points.length - 1];
15331
+ const last = points[points.length - 1];
15316
15332
  if (duration > last.t) push(duration, last.v);
15317
15333
  return out;
15318
15334
  }
@@ -15429,7 +15445,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
15429
15445
  failure: failure2
15430
15446
  };
15431
15447
  }
15432
- let failure = result.success ? void 0 : ffmpegFailure(stage, result);
15448
+ let failure = result.success ? void 0 : ffmpegFailure(stage, result, ffmpegProcessTimeout);
15433
15449
  const probedLinkFailedToRead = readsUrl && (failure?.reason === "invalid_media" || failure?.reason === "ffmpeg_failed");
15434
15450
  if (failure && probedLinkFailedToRead) {
15435
15451
  failure = { ...failure, reason: "download_failed", owner: "system", retryable: true };
@@ -15475,7 +15491,7 @@ async function generateSilence(outputPath, duration, signal, config) {
15475
15491
  failure: failure2
15476
15492
  };
15477
15493
  }
15478
- const failure = !result.success ? ffmpegFailure("silence", result) : void 0;
15494
+ const failure = !result.success ? ffmpegFailure("silence", result, ffmpegProcessTimeout) : void 0;
15479
15495
  return {
15480
15496
  success: result.success,
15481
15497
  outputPath,
@@ -15584,7 +15600,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
15584
15600
  };
15585
15601
  }
15586
15602
  if (!result.success) {
15587
- const failure = ffmpegFailure("mix", result);
15603
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15588
15604
  return {
15589
15605
  success: false,
15590
15606
  outputPath,
@@ -15609,14 +15625,15 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
15609
15625
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
15610
15626
  const outputDir = dirname7(outputPath);
15611
15627
  if (!existsSync11(outputDir)) mkdirSync7(outputDir, { recursive: true });
15612
- const buildInputFilters = (ignoreKeyframes) => memberTracks.map((track, i) => {
15628
+ const buildInputFilters = (ignoreKeyframes, padMembers) => memberTracks.map((track, i) => {
15613
15629
  const volumeFilter = buildVolumeExpression(track, ignoreKeyframes);
15614
- return buildTrackInputFilter(track, i, volumeFilter, totalDuration);
15630
+ return buildTrackInputFilter(track, i, volumeFilter, totalDuration, padMembers);
15615
15631
  });
15616
15632
  const mixInputs = memberTracks.map((_, i) => `[a${i}]`).join("");
15617
15633
  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]` : (
15634
+ const inputFilters = buildInputFilters(ignoreKeyframes, !useNormalize2);
15635
+ const padUnitySumOnceToFilm = `[mixed];[mixed]apad,asetpts=N/SR/TB,atrim=0:${formatFilterNumber(totalDuration)}`;
15636
+ const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0${padUnitySumOnceToFilm}[out]` : (
15620
15637
  // Every member is padded and trimmed to totalDuration above, and
15621
15638
  // dropout_transition=0 keeps all N inputs active for that whole span.
15622
15639
  // amix's default normalize therefore divides by exactly N throughout;
@@ -15685,7 +15702,7 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
15685
15702
  }
15686
15703
  }
15687
15704
  if (signal?.aborted || !result.success) {
15688
- const failure = ffmpegFailure("mix", result);
15705
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15689
15706
  return { success: false, error: failure.detail, failure };
15690
15707
  }
15691
15708
  return { success: true, degradedAutomation };
@@ -15703,7 +15720,11 @@ async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, tota
15703
15720
  timeout: config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15704
15721
  });
15705
15722
  if (!encoded.success) {
15706
- const failure = ffmpegFailure("mix", encoded);
15723
+ const failure = ffmpegFailure(
15724
+ "mix",
15725
+ encoded,
15726
+ config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15727
+ );
15707
15728
  return {
15708
15729
  success: false,
15709
15730
  outputPath,
@@ -15868,11 +15889,17 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15868
15889
  }
15869
15890
  audioSrcPath = trimmedPath;
15870
15891
  }
15871
- const automation = element.automation ? resolveAutomation(
15872
- parseAutomation(element.automation),
15873
- element.fxChain ? parseAudioFxChain(element.fxChain) : void 0
15892
+ const chain = element.fxChain ? parseAudioFxChain(element.fxChain) : void 0;
15893
+ const automation = element.automation ? resolveAutomation(parseAutomation(element.automation), chain) : null;
15894
+ const gainBeforeFx = chain !== void 0 && enabledAudioFxNodes(chain).length > 0;
15895
+ const laneKeyframes = automation ? volumeLaneKeyframes(
15896
+ automation,
15897
+ element.start,
15898
+ element.end - element.start,
15899
+ element.volume ?? 1,
15900
+ gainBeforeFx
15874
15901
  ) : null;
15875
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, element.start, element.end - element.start) : null;
15902
+ const tweened = !laneKeyframes && (element.volumeKeyframes?.length ?? 0) > 0;
15876
15903
  const envelopeKeyframes = laneKeyframes ?? element.volumeKeyframes;
15877
15904
  const envelope = envelopeKeyframes && envelopeKeyframes.length > 0 ? {
15878
15905
  keyframes: envelopeKeyframes,
@@ -15881,8 +15908,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15881
15908
  } : null;
15882
15909
  let bakedEnvelope = false;
15883
15910
  let tailSeconds = 0;
15884
- if (element.fxChain) {
15885
- const chain = parseAudioFxChain(element.fxChain);
15911
+ if (chain) {
15886
15912
  tailSeconds = chainTailSeconds(chain, automation ?? void 0);
15887
15913
  const fxResult = await applyAudioFxChain(
15888
15914
  audioSrcPath,
@@ -15892,7 +15918,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15892
15918
  trackId: element.id,
15893
15919
  signal: effectiveSignal,
15894
15920
  ...automation ? { automation } : {},
15895
- ...envelope ? { envelope } : {}
15921
+ ...envelope ? { envelope } : {},
15922
+ ...gainBeforeFx ? { inputGain: clipGain(element.volume ?? 1, tweened) } : {}
15896
15923
  }
15897
15924
  );
15898
15925
  audioSrcPath = fxResult.path;
@@ -15914,7 +15941,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15914
15941
  mediaStart: element.mediaStart,
15915
15942
  duration: element.end - element.start,
15916
15943
  // Gain is already in the samples when baked, so mix at unity.
15917
- volume: bakedEnvelope ? 1 : element.volume ?? 1,
15944
+ volume: bakedEnvelope || gainBeforeFx && !tweened ? 1 : element.volume ?? 1,
15918
15945
  volumeKeyframes: bakedEnvelope ? void 0 : envelopeKeyframes ?? void 0,
15919
15946
  ...element.fadeIn ? { fadeIn: element.fadeIn } : {},
15920
15947
  ...element.fadeOut ? { fadeOut: element.fadeOut } : {},
@@ -16031,7 +16058,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
16031
16058
  parseAutomation(meta.automation),
16032
16059
  meta.fxChain ? parseAudioFxChain(meta.fxChain) : void 0
16033
16060
  ) : null;
16034
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration) : null;
16061
+ const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration, meta.volume) : null;
16035
16062
  const envelope = laneKeyframes && laneKeyframes.length > 0 ? { keyframes: laneKeyframes, trackStart: 0, baseVolume: meta.volume } : null;
16036
16063
  let groupSrcPath = groupWavPath;
16037
16064
  let bakedEnvelope = false;
@@ -17316,6 +17343,7 @@ export {
17316
17343
  assertConfiguredFfmpegBinariesExist,
17317
17344
  cloneCaptureWarning,
17318
17345
  cloneCaptureWarnings,
17346
+ installMediaRenderIdBridge,
17319
17347
  createChromeMemorySampler,
17320
17348
  CaptureFailure,
17321
17349
  classifyCaptureFailure,
@@ -17344,6 +17372,7 @@ export {
17344
17372
  getCompositionDuration,
17345
17373
  percentileOf,
17346
17374
  getCapturePerfSummary,
17375
+ frameSequencePath,
17347
17376
  ManagedChildProcess,
17348
17377
  trackChildProcess,
17349
17378
  killTrackedProcesses,