hyperframes 0.8.149 → 0.8.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-MG53577L.js → add-QKZZG7A2.js} +12 -12
- package/dist/{animatedGifPrep-HV4ZFVPV.js → animatedGifPrep-YQDPYGOD.js} +7 -7
- package/dist/api.js +17 -17
- package/dist/{asset-3GIBXLLL.js → asset-3Q737Y4H.js} +2 -2
- package/dist/{assetCataloger-JJQ4JW2P.js → assetCataloger-BTHEBJJB.js} +3 -3
- package/dist/{auth-XQMT727H.js → auth-XL7EU3SO.js} +1 -1
- package/dist/{autoUpdate-CREZJGMU.js → autoUpdate-PJFLTQ3U.js} +2 -2
- package/dist/{backgroundChecks-K3S5QKR3.js → backgroundChecks-T5TIR5WK.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-42AKNX73.js → batchRender-NFLVIDB4.js} +4 -4
- package/dist/{beats-Y7IKETUS.js → beats-JU4GZ26R.js} +13 -13
- package/dist/{benchmark-22SAXSO5.js → benchmark-7U2YWLTW.js} +9 -9
- package/dist/{browser-ABNISCC3.js → browser-D57UPYDA.js} +5 -5
- package/dist/{bundleWithLocalizedFonts-OE5ZI4JE.js → bundleWithLocalizedFonts-26MXQOZG.js} +2 -2
- package/dist/{capture-DBNHO4WJ.js → capture-47BV25G3.js} +2 -2
- package/dist/{capture-HQG5W42I.js → capture-LT252COQ.js} +11 -11
- package/dist/{catalog-KICZFIDX.js → catalog-BEZXOV4O.js} +12 -12
- package/dist/{catch-up-ZCQUCI45.js → catch-up-KVV5X6BF.js} +4 -4
- package/dist/{check-TGXIICEF.js → check-6KQ433E6.js} +191 -55
- package/dist/{checkBrowser-3N23I56J.js → checkBrowser-PRDR6XBY.js} +172 -30
- package/dist/{chunk-GESJSXFH.js → chunk-35PWWBGZ.js} +1 -1
- package/dist/{chunk-62RUQJ5J.js → chunk-3SVC7SW4.js} +47 -21
- package/dist/{chunk-KMJLHHBF.js → chunk-3ZPFRDEJ.js} +1 -1
- package/dist/{chunk-XHBX7QN6.js → chunk-4LE27ST4.js} +1 -1
- package/dist/{chunk-DUUFDLC4.js → chunk-4MLYMDQ4.js} +1 -1
- package/dist/{chunk-WX7VCRM7.js → chunk-4PKQEKAI.js} +2 -2
- package/dist/{chunk-ZKDKFJCK.js → chunk-4YZYYP7V.js} +5 -5
- package/dist/{chunk-XPQSL5VS.js → chunk-54DMWFQZ.js} +8 -1
- package/dist/{chunk-NKM53WMI.js → chunk-BRF3O35X.js} +7 -7
- package/dist/chunk-DHPFXAUF.js +165 -0
- package/dist/{chunk-CT6ZQRLD.js → chunk-DIOBKUBV.js} +2 -2
- package/dist/{chunk-SP6XL3NP.js → chunk-FAIYEIZ4.js} +1 -1
- package/dist/{chunk-CYFFYK2F.js → chunk-FLVI7FHU.js} +1 -1
- package/dist/{chunk-JDB65PPT.js → chunk-GNTVYF7E.js} +8 -7
- package/dist/{chunk-KQHJKJ3V.js → chunk-HVXRYMGQ.js} +1 -1
- package/dist/{chunk-CNPJHUOX.js → chunk-IP73NMQT.js} +11 -11
- package/dist/{chunk-VX4AL2FQ.js → chunk-IPDS2SWI.js} +1 -1
- package/dist/{chunk-LACFOFWX.js → chunk-IZ7L6V76.js} +1 -1
- package/dist/{chunk-VC7JBNPS.js → chunk-IZPN423R.js} +481 -68
- package/dist/{chunk-P7NTODQE.js → chunk-KQGWP4KH.js} +2 -2
- package/dist/{chunk-SFUBSJFP.js → chunk-KXX6NLOT.js} +1 -1
- package/dist/{chunk-K44HCHZN.js → chunk-L3N7RJHS.js} +3 -3
- package/dist/{chunk-MI6U3OIN.js → chunk-L6RIMCMG.js} +3 -3
- package/dist/{chunk-7IEGPJ4P.js → chunk-LBT4AMSF.js} +1 -1
- package/dist/{chunk-AY2BONTF.js → chunk-LKQS72D5.js} +1 -1
- package/dist/{chunk-VXM4BH23.js → chunk-N3I6RAOR.js} +136 -54
- package/dist/{chunk-Q2Y5OZCY.js → chunk-N4IXP3MY.js} +2 -2
- package/dist/{chunk-LCILS4AF.js → chunk-O3DUX6P2.js} +5 -5
- package/dist/{chunk-MXB6CWVH.js → chunk-OSAH7S5M.js} +1 -1
- package/dist/{chunk-YDRX42P3.js → chunk-OZ4K46OV.js} +3 -3
- package/dist/{chunk-ZK3BQXHC.js → chunk-POEEDPJI.js} +1 -1
- package/dist/{chunk-LTJDUBSQ.js → chunk-PUIELIFA.js} +1 -1
- package/dist/chunk-QBYHNRHU.js +3795 -0
- package/dist/{chunk-KUTT3JXL.js → chunk-QF7AE3DB.js} +3 -3
- package/dist/{chunk-BNYRD7PQ.js → chunk-QLGBNGXC.js} +2 -2
- package/dist/{chunk-U7SASZC5.js → chunk-RJRGR5KF.js} +10 -10
- package/dist/{chunk-4S4VUXW3.js → chunk-RRBKEHST.js} +1 -1
- package/dist/{chunk-TPIG7ZBX.js → chunk-RYL6ZIJP.js} +6 -6
- package/dist/{chunk-L274XRWA.js → chunk-S22FKF6G.js} +6 -3
- package/dist/{chunk-OMBSUO4T.js → chunk-TCWMDCBX.js} +2 -2
- package/dist/{chunk-YA62XGXS.js → chunk-UJKQWGFF.js} +1 -1
- package/dist/{chunk-M5B42RL5.js → chunk-WUUOFMHU.js} +3 -3
- package/dist/{chunk-PR7QQBSC.js → chunk-XGLNQDLN.js} +1 -1
- package/dist/{chunk-VGCEMOQA.js → chunk-XKKBIFE5.js} +7 -7
- package/dist/{chunk-2HWB4TCI.js → chunk-XNDXENJQ.js} +27 -12
- package/dist/{chunk-AHBYQRGI.js → chunk-XQKVEQNY.js} +2 -2
- package/dist/{chunk-ZFN6GQU4.js → chunk-XXPYZBZ3.js} +1 -1
- package/dist/{chunk-K4RIT6PX.js → chunk-XYHBVSWD.js} +2 -2
- package/dist/{chunk-RMIFSBRV.js → chunk-YEBKEWKB.js} +1 -1
- package/dist/{chunk-CZQLCBWY.js → chunk-YG2Y55KF.js} +1 -1
- package/dist/{chunk-B3XLIBFL.js → chunk-YIJMVFEJ.js} +1 -1
- package/dist/{chunk-K35E45SI.js → chunk-YZTVHRJ2.js} +1 -1
- package/dist/{chunk-Y475GD4L.js → chunk-ZD3RDNZI.js} +1 -1
- package/dist/{chunk-QIJLIULK.js → chunk-ZGCRVM5M.js} +2 -2
- package/dist/{clean-6QP7VAV4.js → clean-RLTHFTQA.js} +18 -18
- package/dist/cli.js +52 -52
- package/dist/{cloud-5TME5ROM.js → cloud-Q5TGBTW3.js} +4 -4
- package/dist/{cloudrun-F5VVCVF5.js → cloudrun-4UZF74MV.js} +4 -4
- package/dist/{compare-XPY2EXX6.js → compare-Q5UNR2V4.js} +25 -25
- package/dist/{compiler-NPD3MYMO.js → compiler-ZXT27Z62.js} +4 -4
- package/dist/{component-OPVL3PUX.js → component-H734P32V.js} +3 -3
- package/dist/{compositions-WSEOI454.js → compositions-YEHDSTDR.js} +11 -11
- package/dist/{delete-SLMH57VH.js → delete-LDBMPWRT.js} +2 -2
- package/dist/{dist-CUIR3GFX.js → dist-7JXLFD6U.js} +3 -3
- package/dist/{dist-HUMFCJ6T.js → dist-RGE4QASU.js} +1 -1
- package/dist/{doctor-QZW3EKIH.js → doctor-XNJFIWJ7.js} +13 -13
- package/dist/{events-FIFWFRGC.js → events-G6UFLNXR.js} +2 -2
- package/dist/{events-6C3ZG7LS.js → events-UKCPUWMH.js} +3 -3
- package/dist/{feedback-VKXM7YJ5.js → feedback-CK3KZ7DE.js} +7 -7
- package/dist/{ffmpeg-TZB5WAYE.js → ffmpeg-MJFMBBSF.js} +2 -2
- package/dist/{figma-3UYNADNA.js → figma-3D5C737B.js} +3 -3
- package/dist/fontLocalizeCli.js +9 -9
- package/dist/{get-VH7VMRVX.js → get-QGTQUWFT.js} +2 -2
- package/dist/{gpuPolicy-M2JZGGVW.js → gpuPolicy-G5QAQDYF.js} +7 -7
- package/dist/{grade-compare-OSC2YQWN.js → grade-compare-XIHOM62P.js} +27 -27
- package/dist/{help-DCBFEZPT.js → help-U5UBXT52.js} +1 -1
- package/dist/{history-XYJMQE26.js → history-5J73QZSA.js} +5 -5
- package/dist/hyperframe-runtime.js +65 -58
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -58
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/{info-YEHGENAH.js → info-RJHQQQUP.js} +8 -8
- package/dist/{init-64SML7PY.js → init-M6UQOYYC.js} +29 -29
- package/dist/{inspect-GFOQKRJI.js → inspect-PRQWDBTP.js} +26 -26
- package/dist/{installer-Q4MMZKLA.js → installer-ZL7UKYK5.js} +5 -5
- package/dist/{keyframes-ORYFWKBZ.js → keyframes-VMUJYTCQ.js} +6 -6
- package/dist/{lambda-DRMOJGTC.js → lambda-ON42DRQD.js} +5 -5
- package/dist/{layout-ZEQBE6UQ.js → layout-QKBFVDVY.js} +26 -26
- package/dist/{lint-67KGFY63.js → lint-ZQHHL2SV.js} +7 -7
- package/dist/{lintProject-SSEXWCH6.js → lintProject-3Z6OJCSO.js} +1 -1
- package/dist/{list-T6OU5ARJ.js → list-YBRKIWGF.js} +2 -2
- package/dist/{login-2PXIGDP7.js → login-4B2YXGLB.js} +4 -4
- package/dist/{manager-ES2UFT4X.js → manager-X44GKUEL.js} +3 -3
- package/dist/{manager-GWU5J6RJ.js → manager-ZLJD3EAY.js} +2 -2
- package/dist/{media-treatment-NAAHID6M.js → media-treatment-5C5EJAAP.js} +12 -12
- package/dist/{models-P6XB6RWK.js → models-R7ZDWBJ6.js} +13 -13
- package/dist/{motionShot-7MHRIV6V.js → motionShot-ZHESZNUL.js} +14 -14
- package/dist/{normalize-audio-LWNFYR2F.js → normalize-audio-EVWZVTBM.js} +6 -6
- package/dist/{open-DAP5C7UD.js → open-TV6HT4UT.js} +4 -4
- package/dist/{parakeetRunner-3ZVFPOD4.js → parakeetRunner-YVVF4CG6.js} +14 -14
- package/dist/{pipeline-V4YJCPUX.js → pipeline-ETR5SEBS.js} +9 -9
- package/dist/{play-UDCESJO3.js → play-C6WONRZV.js} +19 -19
- package/dist/{preflight-JOD4OXGC.js → preflight-JYOX64YP.js} +4 -4
- package/dist/{present-DXTHBH3F.js → present-RANXTVPA.js} +15 -15
- package/dist/{preview-YHXKRXKB.js → preview-KF6UMBT2.js} +19 -19
- package/dist/{producer-343ENLAM.js → producer-CY2GXBWN.js} +1 -1
- package/dist/{projectConfig-YXKU3CTB.js → projectConfig-KPXCNMBM.js} +7 -7
- package/dist/{publication-ODXP3JI7.js → publication-Y36N4A7E.js} +3 -3
- package/dist/{publish-UEFBFGV3.js → publish-RCQJO35C.js} +9 -9
- package/dist/{remove-background-FMV6IDOH.js → remove-background-3ZMXVWLB.js} +10 -10
- package/dist/{render-ZTXNPRGF.js → render-FKVRCDT2.js} +58 -35
- package/dist/{render-N7QGX6RJ.js → render-PTNEFO5U.js} +9 -9
- package/dist/{render-SYC4RPGT.js → render-WUSTRMUD.js} +4 -4
- package/dist/{render-batch-SZRPCRUJ.js → render-batch-AQHU2JWZ.js} +4 -4
- package/dist/renderSetupWorker.js +4 -4
- package/dist/{resolver-7MYXNEAX.js → resolver-L5BDZJKU.js} +5 -5
- package/dist/{sherpa-M4Z2Q5Y2.js → sherpa-3S3ISG7G.js} +13 -13
- package/dist/skills/hyperframes/references/frame-worker-core.md +11 -9
- package/dist/skills/hyperframes/references/subagent-dispatch.md +1 -1
- package/dist/skills/media-use/audio/scripts/lib/tts.cleanup.test.mjs +175 -0
- package/dist/skills/media-use/audio/scripts/lib/tts.mjs +24 -19
- package/dist/skills/media-use/references/grading.md +1 -2
- package/dist/skills/media-use/references/meta.md +17 -17
- package/dist/skills/media-use/references/operations.md +6 -6
- package/dist/skills/media-use/references/setup-providers.md +3 -4
- package/dist/skills/media-use/scripts/lib/cutlist.mjs +9 -2
- package/dist/skills/media-use/scripts/lib/cutlist.test.mjs +12 -0
- package/dist/skills/media-use/scripts/lib/telemetry.mjs +4 -1
- package/dist/skills/media-use/scripts/lib/telemetry.test.mjs +20 -0
- package/dist/skills/media-use/scripts/transcript-cut.mjs +23 -4
- package/dist/skills/media-use/scripts/transcript-cut.test.mjs +224 -0
- package/dist/{skills-NEHNSHZK.js → skills-X4WQEKNF.js} +5 -5
- package/dist/{skillsUpdateCheck-O77QR72A.js → skillsUpdateCheck-UYCVHDZ6.js} +3 -3
- package/dist/{snapshot-GDVXFBVY.js → snapshot-YEYHQEZQ.js} +252 -141
- package/dist/{src-VMTHSARJ.js → src-2NN2WB6X.js} +10 -6
- package/dist/{src-PTIV7IVV.js → src-OLBDPEG6.js} +8 -8
- package/dist/{staticProjectServer-3D2NXQ6W.js → staticProjectServer-7H35NMWB.js} +15 -15
- package/dist/studio/assets/{hyperframes-player-C-2YwYre.js → hyperframes-player-DJt8E7k4.js} +1 -1
- package/dist/studio/assets/{index-ByJU5J6n.js → index-BGks9KLI.js} +131 -131
- package/dist/studio/assets/{index-BybGk67U.js → index-DWIHrAY2.js} +1 -1
- package/dist/studio/assets/{index-Cfs2r3fz.js → index-DgbLblGR.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-XIBDLRHB.js → studio-api-HGM2A5I3.js} +9 -9
- package/dist/{studioServer-QIQK47UN.js → studioServer-M6XFL7N6.js} +45 -45
- package/dist/{telemetry-565KDZUW.js → telemetry-KVCZKVTE.js} +3 -3
- package/dist/{timeline-E6XB7I6W.js → timeline-VYYKZR4V.js} +68 -15
- package/dist/{tokens-3ZAJENSE.js → tokens-B5NFFL55.js} +2 -2
- package/dist/{transcribe-XWQQCCBG.js → transcribe-SBETOIWI.js} +12 -12
- package/dist/{transcribe-WODP3T77.js → transcribe-WNFRYZAC.js} +18 -18
- package/dist/{updateCheck-QPYU4GRL.js → updateCheck-5MM2DFUC.js} +2 -2
- package/dist/{upgrade-2FJ5ZEN6.js → upgrade-LWID54R3.js} +2 -2
- package/dist/{urlDownloader-352AY25I.js → urlDownloader-CVLNXFDG.js} +6 -6
- package/dist/{validate-SP2VGNEI.js → validate-V4CGQ6OF.js} +20 -20
- package/dist/{video-RVNPFEA6.js → video-VJB2KRSS.js} +7 -7
- package/package.json +1 -1
- package/dist/chunk-3WD4JNMU.js +0 -3795
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
renderFrameIdForRenderId,
|
|
27
27
|
resolveAuthoredTimingWindow,
|
|
28
28
|
toFps
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-XQKVEQNY.js";
|
|
30
30
|
import {
|
|
31
31
|
AUDIO_GROUP_RENDER_ID_ATTR,
|
|
32
32
|
COLOR_GRADING_SOURCE_HIDDEN_ATTR,
|
|
@@ -55,11 +55,11 @@ import {
|
|
|
55
55
|
shiftRateLane,
|
|
56
56
|
sourceTimeAt,
|
|
57
57
|
timeAtSourceTime
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-QBYHNRHU.js";
|
|
59
59
|
import {
|
|
60
60
|
clampAudioGain,
|
|
61
61
|
isAudibleVideoElement
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-UJKQWGFF.js";
|
|
63
63
|
import {
|
|
64
64
|
redactTelemetryString
|
|
65
65
|
} from "./chunk-T76PLPD6.js";
|
|
@@ -559,6 +559,7 @@ function applyConcreteGpuScreenshotClamp(currentForceScreenshot, resolvedGpuMode
|
|
|
559
559
|
// ../engine/src/utils/sdrCaptureColor.ts
|
|
560
560
|
var SDR_RGB_TO_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
|
|
561
561
|
var SDR_CAPTURE_TO_BT709_FILTER = `scale=in_color_matrix=bt601:in_range=pc:flags=neighbor,format=gbrp,${SDR_RGB_TO_BT709_FILTER}`;
|
|
562
|
+
var SDR_RGB_TO_TAGGED_BT709_FILTER = `${SDR_RGB_TO_BT709_FILTER},setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv`;
|
|
562
563
|
|
|
563
564
|
// ../parsers/dist/htmlAttributeSpans.js
|
|
564
565
|
function readAttributeValue(html, at) {
|
|
@@ -1593,6 +1594,9 @@ identically under Docker. Consider:
|
|
|
1593
1594
|
}
|
|
1594
1595
|
|
|
1595
1596
|
// ../engine/src/services/frameCapture.ts
|
|
1597
|
+
import {
|
|
1598
|
+
CDPSession
|
|
1599
|
+
} from "puppeteer-core";
|
|
1596
1600
|
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
|
|
1597
1601
|
import { join as join3 } from "path";
|
|
1598
1602
|
|
|
@@ -2417,6 +2421,91 @@ var MotionBlurAccumulator = class {
|
|
|
2417
2421
|
}
|
|
2418
2422
|
};
|
|
2419
2423
|
|
|
2424
|
+
// ../engine/src/services/videoFrameTransport.ts
|
|
2425
|
+
import { randomUUID } from "crypto";
|
|
2426
|
+
var base64ImagePrefix = /^data:(image\/[\w.+-]+);base64,/;
|
|
2427
|
+
var transports = /* @__PURE__ */ new WeakMap();
|
|
2428
|
+
async function createTransport(page) {
|
|
2429
|
+
const pageUrl = new URL(page.url());
|
|
2430
|
+
const origin = /^https?:$/.test(pageUrl.protocol) ? pageUrl.origin : "https://hyperframes-render.invalid";
|
|
2431
|
+
const prefix = `${origin}/__hyperframes_video_frames__/${randomUUID()}/`;
|
|
2432
|
+
const responses = /* @__PURE__ */ new Map();
|
|
2433
|
+
const client = await page.createCDPSession();
|
|
2434
|
+
client.on("Fetch.requestPaused", (event) => {
|
|
2435
|
+
const frame = responses.get(event.request.url);
|
|
2436
|
+
void fulfillFrame(client, event.requestId, frame);
|
|
2437
|
+
});
|
|
2438
|
+
try {
|
|
2439
|
+
await client.send("Fetch.enable", {
|
|
2440
|
+
patterns: [{ urlPattern: `${prefix}*`, requestStage: "Request" }]
|
|
2441
|
+
});
|
|
2442
|
+
} catch (error) {
|
|
2443
|
+
await client.detach().catch(() => {
|
|
2444
|
+
});
|
|
2445
|
+
throw error;
|
|
2446
|
+
}
|
|
2447
|
+
page.once("close", () => responses.clear());
|
|
2448
|
+
return { prefix, nextId: 0, responses };
|
|
2449
|
+
}
|
|
2450
|
+
async function fulfillFrame(client, requestId, frame) {
|
|
2451
|
+
try {
|
|
2452
|
+
await client.send("Fetch.fulfillRequest", {
|
|
2453
|
+
requestId,
|
|
2454
|
+
responseCode: frame ? 200 : 404,
|
|
2455
|
+
responseHeaders: [
|
|
2456
|
+
{ name: "Content-Type", value: frame?.contentType ?? "text/plain" },
|
|
2457
|
+
{ name: "Cache-Control", value: "no-store" },
|
|
2458
|
+
{ name: "Access-Control-Allow-Origin", value: "*" }
|
|
2459
|
+
],
|
|
2460
|
+
body: frame?.body ?? ""
|
|
2461
|
+
});
|
|
2462
|
+
} catch (error) {
|
|
2463
|
+
if (frame) frame.error = error;
|
|
2464
|
+
await client.send("Fetch.failRequest", { requestId, errorReason: "Failed" }).catch(() => {
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
async function withVideoFrameSources(page, updates, inject) {
|
|
2469
|
+
if (!updates.some((update) => base64ImagePrefix.test(update.dataUri))) return inject(updates);
|
|
2470
|
+
let pending = transports.get(page);
|
|
2471
|
+
if (!pending) {
|
|
2472
|
+
pending = createTransport(page);
|
|
2473
|
+
transports.set(page, pending);
|
|
2474
|
+
}
|
|
2475
|
+
let transport;
|
|
2476
|
+
try {
|
|
2477
|
+
transport = await pending;
|
|
2478
|
+
} catch (error) {
|
|
2479
|
+
transports.delete(page);
|
|
2480
|
+
throw error;
|
|
2481
|
+
}
|
|
2482
|
+
const registered = [];
|
|
2483
|
+
try {
|
|
2484
|
+
const resolved = updates.map((update) => {
|
|
2485
|
+
const match = base64ImagePrefix.exec(update.dataUri);
|
|
2486
|
+
if (!match) return update;
|
|
2487
|
+
const frame = {
|
|
2488
|
+
contentType: match[1] ?? "image/png",
|
|
2489
|
+
body: update.dataUri.slice(match[0].length)
|
|
2490
|
+
};
|
|
2491
|
+
const url = `${transport.prefix}${transport.nextId++}`;
|
|
2492
|
+
transport.responses.set(url, frame);
|
|
2493
|
+
registered.push({ url, frame });
|
|
2494
|
+
return {
|
|
2495
|
+
...update,
|
|
2496
|
+
dataUri: url,
|
|
2497
|
+
crossOrigin: "anonymous",
|
|
2498
|
+
sourceLabel: "inline frame"
|
|
2499
|
+
};
|
|
2500
|
+
});
|
|
2501
|
+
return await inject(resolved);
|
|
2502
|
+
} catch (error) {
|
|
2503
|
+
throw registered.find(({ frame }) => frame.error !== void 0)?.frame.error ?? error;
|
|
2504
|
+
} finally {
|
|
2505
|
+
for (const { url } of registered) transport.responses.delete(url);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2420
2509
|
// ../engine/src/services/screenshotService.ts
|
|
2421
2510
|
var cdpSessionCache = /* @__PURE__ */ new WeakMap();
|
|
2422
2511
|
async function getCdpSession(page) {
|
|
@@ -2740,6 +2829,13 @@ async function ensureRenderFrameSiblings(page) {
|
|
|
2740
2829
|
}
|
|
2741
2830
|
async function injectVideoFramesBatch(page, updates) {
|
|
2742
2831
|
if (updates.length === 0) return [];
|
|
2832
|
+
return withVideoFrameSources(
|
|
2833
|
+
page,
|
|
2834
|
+
updates,
|
|
2835
|
+
(resolved) => injectResolvedVideoFramesBatch(page, resolved)
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
async function injectResolvedVideoFramesBatch(page, updates) {
|
|
2743
2839
|
return await page.evaluate(
|
|
2744
2840
|
// fallow-ignore-next-line complexity
|
|
2745
2841
|
async (items, visualProperties, colorGradingSourceHiddenAttr) => {
|
|
@@ -2818,8 +2914,10 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
2818
2914
|
img.style.zIndex = computedStyle.zIndex;
|
|
2819
2915
|
img.decoding = "sync";
|
|
2820
2916
|
if (img.getAttribute("src") !== item.dataUri) {
|
|
2917
|
+
if (item.crossOrigin) img.crossOrigin = item.crossOrigin;
|
|
2918
|
+
else img.removeAttribute("crossorigin");
|
|
2821
2919
|
img.src = item.dataUri;
|
|
2822
|
-
const source = item.dataUri.startsWith("data:") ? "inline frame" : item.dataUri;
|
|
2920
|
+
const source = item.sourceLabel ?? (item.dataUri.startsWith("data:") ? "inline frame" : item.dataUri);
|
|
2823
2921
|
pendingDecodes.push(
|
|
2824
2922
|
img.decode().catch((error) => {
|
|
2825
2923
|
throw new Error(
|
|
@@ -5212,6 +5310,13 @@ async function constructCaptureSession(input) {
|
|
|
5212
5310
|
const { browser, captureMode } = browserLease;
|
|
5213
5311
|
const page = await browser.newPage();
|
|
5214
5312
|
onPageCreated(page);
|
|
5313
|
+
const frame = page.mainFrame();
|
|
5314
|
+
if ("client" in frame && frame.client instanceof CDPSession) {
|
|
5315
|
+
await frame.client.send("Network.enable", {
|
|
5316
|
+
maxTotalBufferSize: 32 * 1024 * 1024,
|
|
5317
|
+
maxResourceBufferSize: 16 * 1024 * 1024
|
|
5318
|
+
});
|
|
5319
|
+
}
|
|
5215
5320
|
await page.evaluateOnNewDocument(() => {
|
|
5216
5321
|
const w = window;
|
|
5217
5322
|
if (typeof w.__name !== "function") {
|
|
@@ -8875,6 +8980,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
8875
8980
|
}
|
|
8876
8981
|
} else if (codec === "prores") {
|
|
8877
8982
|
args.push("-c:v", "prores_ks", "-profile:v", preset, "-vendor", "apl0");
|
|
8983
|
+
args.push("-vf", SDR_RGB_TO_TAGGED_BT709_FILTER);
|
|
8878
8984
|
args.push("-pix_fmt", pixelFormat);
|
|
8879
8985
|
appendRenderProvenanceArgs(args, outputPath);
|
|
8880
8986
|
return [...args, "-y", outputPath];
|
|
@@ -9536,6 +9642,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
9536
9642
|
}
|
|
9537
9643
|
} else if (codec === "prores") {
|
|
9538
9644
|
args.push("-c:v", "prores_ks", "-profile:v", preset, "-vendor", "apl0");
|
|
9645
|
+
args.push("-vf", SDR_RGB_TO_TAGGED_BT709_FILTER);
|
|
9539
9646
|
args.push("-pix_fmt", pixelFormat);
|
|
9540
9647
|
appendRenderProvenanceArgs(args, outputPath);
|
|
9541
9648
|
return [...args, "-y", outputPath];
|
|
@@ -9759,6 +9866,16 @@ function buildGroup(id, memberIds, el) {
|
|
|
9759
9866
|
hidden: el?.hasAttribute("data-hidden") ?? false
|
|
9760
9867
|
};
|
|
9761
9868
|
}
|
|
9869
|
+
function resolveMasterBusElement(root) {
|
|
9870
|
+
for (const el of root.querySelectorAll("[data-composition-id]")) {
|
|
9871
|
+
if (!el.parentElement?.closest("[data-composition-id]"))
|
|
9872
|
+
return el;
|
|
9873
|
+
}
|
|
9874
|
+
return null;
|
|
9875
|
+
}
|
|
9876
|
+
function readMasterFxChain(root) {
|
|
9877
|
+
return resolveMasterBusElement(root)?.getAttribute(HF_AUDIO_FX_ATTR) || void 0;
|
|
9878
|
+
}
|
|
9762
9879
|
function resolveAudioGroups(root) {
|
|
9763
9880
|
const membersByGroup = /* @__PURE__ */ new Map();
|
|
9764
9881
|
const candidates = root.querySelectorAll(`audio[${HF_AUDIO_GROUP_ATTR}], video[${HF_AUDIO_GROUP_ATTR}]`);
|
|
@@ -9970,7 +10087,7 @@ import {
|
|
|
9970
10087
|
statSync as statSync5,
|
|
9971
10088
|
unlinkSync
|
|
9972
10089
|
} from "fs";
|
|
9973
|
-
import { createHash, randomUUID } from "crypto";
|
|
10090
|
+
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
9974
10091
|
import { BlockList, isIP } from "net";
|
|
9975
10092
|
import { dirname as dirname4, extname as extname2, join as join7 } from "path";
|
|
9976
10093
|
import { Readable, Transform } from "stream";
|
|
@@ -10203,7 +10320,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
|
|
|
10203
10320
|
if (error.code !== "EEXIST") throw error;
|
|
10204
10321
|
}
|
|
10205
10322
|
if (createdLock) {
|
|
10206
|
-
const owner = `${CACHE_LOCK_OWNER_PREFIX}${
|
|
10323
|
+
const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
|
|
10207
10324
|
try {
|
|
10208
10325
|
mkdirSync4(join7(lockPath, owner));
|
|
10209
10326
|
const entries = readdirSync3(lockPath);
|
|
@@ -10983,7 +11100,7 @@ function unwrapTemplate(html) {
|
|
|
10983
11100
|
}
|
|
10984
11101
|
|
|
10985
11102
|
// ../engine/src/services/extractionCache.ts
|
|
10986
|
-
import { createHash as createHash2, randomUUID as
|
|
11103
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
10987
11104
|
import {
|
|
10988
11105
|
createReadStream as createReadStream3,
|
|
10989
11106
|
existsSync as existsSync7,
|
|
@@ -11102,7 +11219,7 @@ function lookupCacheEntry(rootDir, input) {
|
|
|
11102
11219
|
return { entry: { dir, keyHash }, hit: complete };
|
|
11103
11220
|
}
|
|
11104
11221
|
function partialCacheEntryDir(entry) {
|
|
11105
|
-
return `${entry.dir}.partial-${process.pid}-${
|
|
11222
|
+
return `${entry.dir}.partial-${process.pid}-${randomUUID3().slice(0, 8)}`;
|
|
11106
11223
|
}
|
|
11107
11224
|
function isTargetExistsRenameError(err) {
|
|
11108
11225
|
const code = err.code;
|
|
@@ -13542,7 +13659,7 @@ async function queryElementStacking(page, nativeHdrIds) {
|
|
|
13542
13659
|
}
|
|
13543
13660
|
|
|
13544
13661
|
// ../engine/src/services/audioMixer.ts
|
|
13545
|
-
import { closeSync as
|
|
13662
|
+
import { closeSync as closeSync4, existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync6, openSync as openSync4, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
13546
13663
|
import { join as join13, dirname as dirname6, isAbsolute, relative } from "path";
|
|
13547
13664
|
|
|
13548
13665
|
// ../engine/src/utils/notMediaPayload.ts
|
|
@@ -13967,7 +14084,7 @@ function knobMax(node, key, automation) {
|
|
|
13967
14084
|
return Number.isFinite(fallback) ? fallback : 0;
|
|
13968
14085
|
return lane.points.reduce((max, p) => Math.max(max, p.v), -Infinity);
|
|
13969
14086
|
}
|
|
13970
|
-
function
|
|
14087
|
+
function delayTailSeconds(time, feedback) {
|
|
13971
14088
|
const gap = Math.min(5, time / 1e3);
|
|
13972
14089
|
if (gap <= 0)
|
|
13973
14090
|
return 0;
|
|
@@ -13976,11 +14093,14 @@ function delayTail(time, feedback) {
|
|
|
13976
14093
|
return gap;
|
|
13977
14094
|
return Math.ceil(Math.log(TAIL_FLOOR) / Math.log(fb)) * gap;
|
|
13978
14095
|
}
|
|
14096
|
+
function reverbRoomSeconds(size) {
|
|
14097
|
+
return 0.6 + Math.max(0, Math.min(1, size)) * 2.6;
|
|
14098
|
+
}
|
|
13979
14099
|
function reverbTail(node, automation) {
|
|
13980
|
-
return knobMax(node, "wet", automation) > 0 ?
|
|
14100
|
+
return knobMax(node, "wet", automation) > 0 ? reverbRoomSeconds(knobMax(node, "size", automation)) : 0;
|
|
13981
14101
|
}
|
|
13982
14102
|
function delayNodeTail(node, automation) {
|
|
13983
|
-
return knobMax(node, "mix", automation) > 0 ?
|
|
14103
|
+
return knobMax(node, "mix", automation) > 0 ? delayTailSeconds(knobMax(node, "time", automation), knobMax(node, "feedback", automation)) : 0;
|
|
13984
14104
|
}
|
|
13985
14105
|
function chorusTail(node, automation) {
|
|
13986
14106
|
return knobMax(node, "mix", automation) > 0 ? knobMax(node, "delay", automation) / 1e3 : 0;
|
|
@@ -14010,17 +14130,56 @@ function chainTailSeconds(chain, automation) {
|
|
|
14010
14130
|
}
|
|
14011
14131
|
|
|
14012
14132
|
// ../engine/src/services/audioFxRender.ts
|
|
14013
|
-
import {
|
|
14133
|
+
import {
|
|
14134
|
+
closeSync as closeSync3,
|
|
14135
|
+
existsSync as existsSync9,
|
|
14136
|
+
mkdtempSync as mkdtempSync5,
|
|
14137
|
+
openSync as openSync3,
|
|
14138
|
+
readFileSync as readFileSync6,
|
|
14139
|
+
readSync,
|
|
14140
|
+
rmSync as rmSync6,
|
|
14141
|
+
statSync as statSync7,
|
|
14142
|
+
writeFileSync as writeFileSync5,
|
|
14143
|
+
writeSync as writeSync2
|
|
14144
|
+
} from "fs";
|
|
14014
14145
|
import { tmpdir as tmpdir3 } from "os";
|
|
14015
14146
|
import { join as join12 } from "path";
|
|
14016
14147
|
import { pathToFileURL } from "url";
|
|
14017
14148
|
|
|
14018
14149
|
// ../core/dist/generated/audio-fx-runtime-inline.js
|
|
14019
|
-
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var we="data-fx-chain",Mt=we.slice(5),W=1,T=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),C=(e=.707,t="Bandwidth: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),R=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[R(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[T("frequency","Frequency",1e3),R(-40,40,0),C(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[T("frequency","Frequency",200,20,2e3),R(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[T("frequency","Frequency",4e3,500,2e4),R(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[T("frequency","Cutoff",300,20,2e4),C(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[T("frequency","Cutoff",8e3,100,2e4),C(.707),K],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Hard ceiling: nothing gets past the limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"truepeak",label:"True-peak limiter",group:"dynamics",description:"Lookahead limiter that holds a 4x estimate of the inter-sample peak at a dBTP ceiling; full-band noise can land up to about 1.7 dB above it. Adds lookahead plus about 0.3 ms of latency.",params:[{kind:"number",key:"ceiling",label:"Ceiling",unit:"dBTP",min:-24,max:0,step:.1,default:-1,hint:"Target true peak, measured on a 4x oversampled signal. Dense full-band sound can end up to about 1.7 dB above it."},{kind:"number",key:"lookahead",label:"Lookahead",unit:"ms",min:.5,max:10,step:.1,default:3,hint:"How far ahead the gain starts falling. Longer is smoother and adds the same latency."},{kind:"number",key:"release",label:"Release",unit:"ms",min:10,max:2e3,step:1,default:80,scale:"log"}],web:"worklet-truepeak"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times, a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],L=new Map(z.map(e=>[e.id,e]));function P(e){return L.get(e)}var Ft=z.map(e=>e.id);function y(e,t){let n=L.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(l=>l.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var x=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Chain file must be a JSON object.");let n=t;if(n.version!==W)throw new x(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new x("Chain file is missing a `nodes` array.");let o=n.nodes.map(ve);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function ve(e,t){if(typeof e!="object"||e===null)throw new x(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!L.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Fe({type:n.type,id:E(n.id),fromCarve:Y(n.fromCarve),fromPreset:E(n.fromPreset),label:E(n.label),fromEq:E(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:Me(n.presetAmount),enabled:n.enabled!==!1,params:y(n.type,n.params??void 0)})}function E(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function Me(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function Fe(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Te(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Te(e,t)+16}var Z=`\nconst TP_OS = 4;\nconst TP_HALF = 64;\nconst TP_BRANCH = 33;\nconst TP_DELAY = 16;\n\nfunction besselI0(x) {\n let sum = 1, term = 1;\n for (let k = 1; k < 40; k++) {\n term *= (x / (2 * k)) * (x / (2 * k));\n sum += term;\n }\n return sum;\n}\n\n/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */\nfunction tpBranches() {\n const beta = 6;\n const norm = besselI0(beta);\n const branches = [];\n for (let p = 0; p < TP_OS; p++) {\n const taps = new Float64Array(TP_BRANCH);\n let sum = 0;\n for (let j = 0; j < TP_BRANCH; j++) {\n const k = p + TP_OS * j;\n if (k > 2 * TP_HALF) continue;\n const t = (k - TP_HALF) / TP_OS;\n const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);\n const r = (k - TP_HALF) / TP_HALF;\n taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);\n sum += taps[j];\n }\n // Unity gain at DC on every branch, so a steady level reads as itself.\n for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;\n branches.push(taps);\n }\n return branches;\n}\n\nclass HfTruePeak extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));\n this.delayLen = this.look + TP_DELAY;\n this.branches = tpBranches();\n this.setLevels();\n this.clear();\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n if (e.data && e.data.__hfReset) { this.clear(); return; }\n this.p = { ...this.p, ...e.data };\n this.setLevels();\n };\n }\n /** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */\n clear() {\n this.ch = [];\n this.rel = 1;\n this.dqIdx = new Int32Array(this.look + 2);\n this.dqVal = new Float64Array(this.look + 2);\n this.dqHead = 0;\n this.dqTail = 0;\n this.t = 0;\n this.avgRing = new Float64Array(this.look + 1).fill(1);\n this.avgPos = 0;\n this.avgSum = this.look + 1;\n }\n setLevels() {\n this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);\n this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));\n }\n channel(c) {\n if (!this.ch[c]) {\n this.ch[c] = {\n delay: new Float32Array(this.delayLen),\n w: 0,\n hist: new Float32Array(TP_BRANCH * 2),\n pos: 0,\n };\n }\n return this.ch[c];\n }\n /** Largest interpolated magnitude across channels for the newest sample. */\n peak(chans, inputs, n) {\n let peak = 0;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n const x = inputs[c][n];\n s.pos = (s.pos + 1) % TP_BRANCH;\n s.hist[s.pos] = x;\n s.hist[s.pos + TP_BRANCH] = x;\n const base = s.pos + TP_BRANCH;\n for (let p = 0; p < TP_OS; p++) {\n const taps = this.branches[p];\n let acc = 0;\n for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];\n const a = acc < 0 ? -acc : acc;\n if (a > peak) peak = a;\n }\n }\n return peak;\n }\n /** Windowed minimum of the required gain over the last look + 1 values. */\n windowMin(v) {\n const cap = this.dqIdx.length;\n while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {\n this.dqTail = (this.dqTail + cap - 1) % cap;\n }\n this.dqIdx[this.dqTail] = this.t;\n this.dqVal[this.dqTail] = v;\n this.dqTail = (this.dqTail + 1) % cap;\n while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {\n this.dqHead = (this.dqHead + 1) % cap;\n }\n this.t++;\n return this.dqVal[this.dqHead];\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const chans = i.length;\n const frames = i[0].length;\n for (let n = 0; n < frames; n++) {\n const peak = this.peak(chans, i, n);\n const need = peak > this.ceiling ? this.ceiling / peak : 1;\n const m = this.windowMin(need);\n this.avgSum += m - this.avgRing[this.avgPos];\n this.avgRing[this.avgPos] = m;\n this.avgPos = (this.avgPos + 1) % this.avgRing.length;\n const target = Math.min(1, this.avgSum / this.avgRing.length);\n const released = this.rel + (1 - this.rel) * this.relCoef;\n this.rel = released < target ? released : target;\n if (target >= 1 && this.rel > 0.9999999) this.rel = 1;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n o[c][n] = s.delay[s.w] * this.rel;\n s.delay[s.w] = i[c][n];\n s.w = (s.w + 1) % this.delayLen;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-truepeak", HfTruePeak);\n`;var Pe=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left, so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n if (this.held[ch] === undefined) this.held[ch] = 0;\n if (this.holds[ch] === undefined) this.holds[ch] = 0;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point, which hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel). Advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n this.phase = 0;\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros, so the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0. A one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal, while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`+Z,S=new WeakMap;var He=new WeakSet;function O(e){let t=S.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(Pe)))}`;await e.audioWorklet.addModule(n),He.add(e)})().catch(n=>{throw S.delete(e),n}),S.set(e,t)),t}function _e(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),l=Math.max(.001,1-Math.max(0,Math.min(1,n))),u=0,d=0;for(let c=0;c<a;c++){u+=l*(s()-u);let b=u*Math.pow(1-c/a,2.5);r[c]=b,d+=b*b}let m=Math.sqrt(d);if(m>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/m;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function ae(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let u=0;u<a;u++){let d=u/a;i[u]=t==="sine"?Math.sin(2*Math.PI*d):4*Math.abs((d+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let l=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,l),s}function oe(e){try{e.stop()}catch{}e.disconnect()}function I(e,t){return[{param:e},{param:t,map:n=>1-n}]}function re(e,t,n){e.gain.value=n,t.gain.value=1-n}function q(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var Ne=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Re=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Ne.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function Ee(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o)=>{let a=new AudioWorkletNode(n,e,{processorOptions:{...o}});return{input:a,output:a,update:r=>a.port.postMessage({...r}),...t?{reset:()=>a.port.postMessage({__hfReset:!0})}:{},dispose:()=>{a.port.postMessage({__hfDispose:!0}),a.disconnect()}}}}var De={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},Ce=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=De[String(i.type)]??Math.tanh,l=Math.pow(10,f(i.threshold)/20),u=8192,d=new Float32Array(u);for(let m=0;m<u;m++){let c=m/(u-1)*2-1;d[m]=s(c/Math.max(1e-6,l))*l}n.curve=d,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Le=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let l=u=>{a.delayTime.value=Math.min(5,f(u.time)/1e3),r.gain.value=f(u.feedback),re(i,s,f(u.mix))};return l(t),{input:n,output:o,update:l,automation:{time:[{param:a.delayTime,map:u=>Math.min(5,B(u))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(u=>u.disconnect())}},Se=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),l=e.createGain(),u=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(l).connect(a),o.connect(u).connect(a);let d=m=>{r.delayTime.value=f(m.delay)/1e3,s.gain.value=f(m.depth)/1e3,i.playbackRate.value=f(m.speed),re(l,u,f(m.mix))};return d(t),{input:o,output:a,update:d,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(l.gain,u.gain)},dispose:()=>{oe(i),[o,a,r,s,l,u].forEach(m=>m.disconnect())}}},Oe=6,Be=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=ae(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),l=e.createGain(),u=e.createGain(),d=e.createGain(),m=[];o.connect(r);let c=r;for(let p=0;p<Oe;p++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,l.connect(h.frequency),c.connect(h),c=h,m.push(h)}s.connect(l),c.connect(u).connect(i),r.connect(d).connect(i),i.connect(a);let b=p=>{let h=1e3/Math.max(.1,f(p.delay));for(let g of m)g.frequency.value=h;l.gain.value=h*f(p.decay),s.playbackRate.value=f(p.speed),r.gain.value=f(p.in_gain),i.gain.value=f(p.out_gain),u.gain.value=1,d.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{oe(s),[o,a,r,i,l,u,d,...m].forEach(p=>p.disconnect())}}},Ie=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",l=u=>{let d=`${f(u.size)}:${f(u.damping)}`;if(d!==s){let m=_e(e.sampleRate,f(u.size),f(u.damping)),c=e.createBuffer(1,m.length,e.sampleRate);c.getChannelData(0).set(m),a.buffer=c,s=d}r.gain.value=f(u.wet),i.gain.value=f(u.dry)};return l(t),{input:n,output:o,update:l,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(u=>u.disconnect())}},qe={"gain-node":Re,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Le,"chorus-lfo":Se,"allpass-phaser":Be,convolver:Ie};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ge(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return Ee(t)(e,r,o);let i=qe[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var m;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),l=o;for(let c of s){let b=null;if(c.preset){let p=e.createGain(),h=e.createGain(),g=e.createGain(),F=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,l.connect(p),p.connect(h).connect(F),b={entry:p,wet:g,dry:h,join:F},l=p}for(let p of c.nodes){let h=Ge(e,p.type,p.params??{},n);l.connect(h.input),l=h.output,r.push({...p.id?{id:p.id}:{},type:p.type,handle:h})}b&&c.preset&&(l.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),l=b.join)}l.connect(a);let u=ne(t),d={};for(let c of i)(d[m=c.id]??(d[m]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:d,nodes:r,update(c){if(ne(c)!==u)return!1;w(c).forEach((p,h)=>{let g=r[h];g&&(g.handle.update(y(p.type,p.params)),p.id===void 0?delete g.id:g.id=p.id)});let b=0;for(let p of te(w(c))){if(!p.preset)continue;let h=i[b++];!h||h.id!==p.preset||(h.wet.gain.value=p.amount,h.dry.gain.value=1-p.amount)}return!0},reset(){for(let{handle:c}of r)c.reset?.()},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:p,join:h}of i)c.disconnect(),b.disconnect(),p.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,U=10;var Ve="data-automation",Gt=Ve.slice(5),D=1,$e=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},V="volume",le="rate";function N(e){if(e===V)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===je){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var je="preset";var Xe={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1},me={min:G,max:U,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=N(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return me;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...Xe,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function We(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function pe(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ke(e){let t=We(e?.curve),n=pe(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function Ye(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ke(e)}}function he(e,t){let n=e.map(a=>Ye(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,$e)}function ze(e){let t=[];for(let n of e.lanes){if(!N(n.target))continue;let o=n.target===V?de:n.target===le?me:null,a=he(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function fe(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=he(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function be(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!N(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return ze({version:D,lanes:o})}function Je(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Qe(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,l=Math.min(r,Math.max(1,i,s));return{cx:e+n/l,cy:t+o/l,w:l}}function Ze(e,t,n){let{cx:o,cy:a,w:r}=Qe(t,n),i=2-2*r,s=e*i-1+2*r*o,l=-e*i-2*r*o,u=et(s,l,e),d=1-u,m=d*d+2*r*u*d+u*u;return m>0?(2*r*a*u*d+u*u)/m:e}function et(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function tt(e,t){let n=pe(t.viaX,t.viaY);return n?Ze(Math.min(1,Math.max(0,e)),n.x,n.y):Je(e,t.curve)}function nt(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function j(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let m=i+s>>1;o[m].t<=t?i=m:s=m}let l=o[i],u=o[s],d=u.t-l.t;return d<=0?u.v:nt(l.v,u.v,tt((t-l.t)/d,l),n)}function ge(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function Ae(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let l=0;l<r;l+=1)i[l]=j(e,t+s*l/(r-1),a);return i}var at=100,ot=8,rt=4096,X=e=>e;function it(e,t,n){return!(!!e.curve||e.viaX!==void 0&&e.viaY!==void 0)&&t==="linear"&&!n}function st(e){let t=Math.ceil(e*at);return Math.min(rt,Math.max(ot,t))}function ut(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,l=a??X,u=v=>r+(v-i)/s,d=e.points[t],m=e.points[t+1],c=u(m.t);if(c<=r)return null;let b=Math.max(r,u(d.t)),p=c-b;if(p<=0)return null;if(it(d,n,a))return{kind:"ramp",time:c,value:l(m.v)};let h=st(p),g=Ae(e,Math.max(d.t,i),m.t,h,n),F=new Float32Array(h);for(let v=0;v<h;v+=1)F[v]=l(g[v]??0);return{kind:"curve",time:b,duration:p,values:F}}function lt(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=ut(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??X)(j(e,n.elapsed,t))}),a}function ct(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{let o=n.values[n.values.length-1]??0;e.linearRampToValueAtTime(o,n.time+n.duration)}}function dt(e){for(let t of e)t.param.cancelScheduledValues(0)}function mt(e,t,n,o){if(t.points.length!==0)for(let a of e){if(dt([a]),ge(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}ct(a.param,lt(t,n,o,a.map))}}function xe(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let l=N(s.target);if(!l)continue;let u=l.kind==="preset"?a?.[l.presetId]:l.kind==="fx"?r.get(l.nodeId)?.automation?.[l.param]:void 0;if(!u||u.length===0)continue;let d=$(s.target,t);d&&(mt(u,s,d.scale,o),i.push(...u))}return i}var pt=5,ht=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function ft(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(ht)/Math.log(o))*n}function bt(e,t){return k(e,"wet",t)>0?.6+Math.max(0,Math.min(1,k(e,"size",t)))*2.6:0}function gt(e,t){return k(e,"mix",t)>0?ft(k(e,"time",t),k(e,"feedback",t)):0}function At(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function xt(e,t){return k(e,"mix",t)>0?.2:0}function yt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return bt(e,t);case"delay":return gt(e,t);case"chorus":return At(e,t);case"pitchshift":return xt(e,t);default:return 0}}function ye(e,t){let n=e.nodes.reduce((o,a)=>o+yt(a,t),0);return Math.min(pt,n)}function ke(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function kt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function wt(e,t){let n=[];for(let o=0;o<e.numberOfChannels;o++)n.push(new Float32Array(e.getChannelData(o).subarray(t)));return n}async function vt(e,t,n,o){let a=J(n),r=Math.max(1,e.length),i=e[0]?.length??0;if(i===0)return e;let s=o?fe(be(o),a):null,l=Math.ceil(ye(a,s??void 0)*t),u=ke(a,t),d=new OfflineAudioContext(r,i+l+u,t);ie(a)&&await O(d);let m=d.createBufferSource();m.buffer=kt(d,e,r,i,t);let c=se(d,a);return s&&xe(s,a,c.nodes,{scheduledAt:0,elapsed:0,rate:1},c.presets),m.connect(c.input),c.output.connect(d.destination),m.start(),wt(await d.startRendering(),u)}window.__HF_AUDIO_FX={render:vt};})();\n';
|
|
14150
|
+
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var we="data-fx-chain",Tt=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}),R=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[R(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[F("frequency","Frequency",1e3),R(-40,40,0),C(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[F("frequency","Frequency",200,20,2e3),R(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),R(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[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"}],L=new Map(z.map(e=>[e.id,e]));function P(e){return L.get(e)}var Ft=z.map(e=>e.id);function y(e,t){let n=L.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(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"||!L.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,S=new WeakMap;var He=new WeakSet;function O(e){let t=S.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(Pe)))}`;await e.audioWorklet.addModule(n),He.add(e)})().catch(n=>{throw S.delete(e),n}),S.set(e,t)),t}function _e(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),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),Re=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Ne.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function Ee(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o,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()}}},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())}},Se=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),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":Re,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Le,"chorus-lfo":Se,"allpass-phaser":Be,convolver:Ie};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ge(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return Ee(t)(e,r,o);let i=qe[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var 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",Ut=Ve.slice(5),D=1,$e=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},V="volume",le="rate";function N(e){if(e===V)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===je){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var je="preset";var Xe={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1},me={min:G,max:U,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=N(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return me;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...Xe,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function We(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function pe(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ke(e){let t=We(e?.curve),n=pe(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function Ye(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ke(e)}}function he(e,t){let n=e.map(a=>Ye(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,$e)}function ze(e){let t=[];for(let n of e.lanes){if(!N(n.target))continue;let o=n.target===V?de:n.target===le?me:null,a=he(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function fe(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=he(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function be(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!N(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return ze({version:D,lanes:o})}function Je(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Qe(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,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){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{let o=n.values[n.values.length-1]??0;e.linearRampToValueAtTime(o,n.time+n.duration)}}function dt(e){for(let t of e)t.param.cancelScheduledValues(0)}function mt(e,t,n,o){if(t.points.length!==0)for(let a of e){if(dt([a]),ge(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}ct(a.param,lt(t,n,o,a.map))}}function xe(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let u=N(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&&(mt(l,s,m.scale,o),i.push(...l))}return i}var pt=5,ht=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function ft(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(ht)/Math.log(o))*n}function bt(e){return .6+Math.max(0,Math.min(1,e))*2.6}function gt(e,t){return k(e,"wet",t)>0?bt(k(e,"size",t)):0}function At(e,t){return k(e,"mix",t)>0?ft(k(e,"time",t),k(e,"feedback",t)):0}function xt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function yt(e,t){return k(e,"mix",t)>0?.2:0}function kt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return gt(e,t);case"delay":return At(e,t);case"chorus":return xt(e,t);case"pitchshift":return yt(e,t);default:return 0}}function ye(e,t){let n=e.nodes.reduce((o,a)=>o+kt(a,t),0);return Math.min(pt,n)}function ke(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function wt(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 vt(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 Mt(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=wt(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(),vt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Mt};})();\n';
|
|
14020
14151
|
function getAudioFxRuntimeScript() {
|
|
14021
14152
|
return AUDIO_FX_RUNTIME_IIFE;
|
|
14022
14153
|
}
|
|
14023
14154
|
|
|
14155
|
+
// ../engine/src/services/audioFxWarmup.ts
|
|
14156
|
+
var DECAY_TO_FLOOR = Math.log(1e3);
|
|
14157
|
+
var param = (node, key) => Number(normalizeAudioFxParams(node.type, node.params)[key] ?? 0);
|
|
14158
|
+
var reverbMemory = (node) => param(node, "wet") > 0 ? reverbRoomSeconds(param(node, "size")) : 0;
|
|
14159
|
+
var delayMemory = (node) => param(node, "mix") > 0 ? delayTailSeconds(param(node, "time"), param(node, "feedback")) : 0;
|
|
14160
|
+
var releaseMemory = (node) => DECAY_TO_FLOOR * param(node, "release") / 1e3;
|
|
14161
|
+
var MEMORY = {
|
|
14162
|
+
reverb: reverbMemory,
|
|
14163
|
+
delay: delayMemory,
|
|
14164
|
+
compressor: releaseMemory,
|
|
14165
|
+
limiter: releaseMemory,
|
|
14166
|
+
truepeak: releaseMemory,
|
|
14167
|
+
gate: releaseMemory
|
|
14168
|
+
};
|
|
14169
|
+
function chainWarmupSeconds(chain) {
|
|
14170
|
+
return enabledAudioFxNodes(chain).reduce(
|
|
14171
|
+
(sum, node) => sum + (MEMORY[node.type]?.(node) ?? 0),
|
|
14172
|
+
0
|
|
14173
|
+
);
|
|
14174
|
+
}
|
|
14175
|
+
function windowPrerollSeconds(chain, floorSeconds, capSeconds) {
|
|
14176
|
+
const needed = chainWarmupSeconds(chain);
|
|
14177
|
+
return {
|
|
14178
|
+
seconds: Math.min(capSeconds, Math.max(floorSeconds, needed)),
|
|
14179
|
+
capped: needed > capSeconds
|
|
14180
|
+
};
|
|
14181
|
+
}
|
|
14182
|
+
|
|
14024
14183
|
// ../engine/src/services/audioFxRender.ts
|
|
14025
14184
|
var AudioFxRenderError = class extends Error {
|
|
14026
14185
|
constructor(message) {
|
|
@@ -14031,6 +14190,8 @@ var AudioFxRenderError = class extends Error {
|
|
|
14031
14190
|
function readWavChunks(buf) {
|
|
14032
14191
|
const head = { format: 1, channels: 1, sampleRate: 48e3, bits: 16 };
|
|
14033
14192
|
let data;
|
|
14193
|
+
let dataBody;
|
|
14194
|
+
let dataSize;
|
|
14034
14195
|
for (const { id, body, size } of riffChunks(buf)) {
|
|
14035
14196
|
if (id === "fmt ") {
|
|
14036
14197
|
const format = wavFormatTag(buf, body, size);
|
|
@@ -14040,11 +14201,41 @@ function readWavChunks(buf) {
|
|
|
14040
14201
|
head.sampleRate = buf.readUInt32LE(body + 4);
|
|
14041
14202
|
head.bits = buf.readUInt16LE(body + 14);
|
|
14042
14203
|
} else if (id === "data") {
|
|
14043
|
-
data = buf.subarray(body,
|
|
14204
|
+
data = buf.subarray(body, body + resolveDataBytes(size, buf.length - body));
|
|
14205
|
+
dataBody = body;
|
|
14206
|
+
dataSize = size;
|
|
14044
14207
|
break;
|
|
14045
14208
|
}
|
|
14046
14209
|
}
|
|
14047
|
-
return { ...head, data };
|
|
14210
|
+
return { ...head, data, dataBody, dataSize };
|
|
14211
|
+
}
|
|
14212
|
+
var MAX_WHOLE_FILE_BYTES = 2 ** 31 - 1;
|
|
14213
|
+
var HEADER_PROBE_BYTES = 64 * 1024;
|
|
14214
|
+
function resolveDataBytes(declared, availableBytes) {
|
|
14215
|
+
return declared === 0 || declared === 4294967295 ? availableBytes : Math.min(declared, availableBytes);
|
|
14216
|
+
}
|
|
14217
|
+
function readWavHeader(path) {
|
|
14218
|
+
const fd = openSync3(path, "r");
|
|
14219
|
+
try {
|
|
14220
|
+
const head = Buffer.alloc(HEADER_PROBE_BYTES);
|
|
14221
|
+
const read = readSync(fd, head, 0, head.length, 0);
|
|
14222
|
+
const buf = head.subarray(0, read);
|
|
14223
|
+
if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF") return null;
|
|
14224
|
+
const { format, channels, sampleRate, bits, dataBody, dataSize } = readWavChunks(buf);
|
|
14225
|
+
if (dataBody === void 0 || dataSize === void 0) return null;
|
|
14226
|
+
const dataBytes = resolveDataBytes(dataSize, statSync7(path).size - dataBody);
|
|
14227
|
+
return {
|
|
14228
|
+
format,
|
|
14229
|
+
channels,
|
|
14230
|
+
sampleRate,
|
|
14231
|
+
bits,
|
|
14232
|
+
float: format === 3 && bits === 32,
|
|
14233
|
+
dataOffset: dataBody,
|
|
14234
|
+
dataBytes
|
|
14235
|
+
};
|
|
14236
|
+
} finally {
|
|
14237
|
+
closeSync3(fd);
|
|
14238
|
+
}
|
|
14048
14239
|
}
|
|
14049
14240
|
function readWav(path) {
|
|
14050
14241
|
const buf = readFileSync6(path);
|
|
@@ -14076,13 +14267,11 @@ function decodeSamples(data, format, bits, path) {
|
|
|
14076
14267
|
}
|
|
14077
14268
|
throw new AudioFxRenderError(`Unsupported WAV format ${format}/${bits}-bit: ${path}`);
|
|
14078
14269
|
}
|
|
14079
|
-
function
|
|
14080
|
-
const n = samples.length;
|
|
14270
|
+
function wavHeader(bytes, sampleRate, channels, float) {
|
|
14081
14271
|
const bytesPerSample = float ? 4 : 2;
|
|
14082
|
-
const
|
|
14083
|
-
const buf = Buffer.alloc(44 + bytes);
|
|
14272
|
+
const buf = Buffer.alloc(44);
|
|
14084
14273
|
buf.write("RIFF", 0, "ascii");
|
|
14085
|
-
buf.writeUInt32LE(36 + bytes, 4);
|
|
14274
|
+
buf.writeUInt32LE(Math.min(4294967295, 36 + bytes), 4);
|
|
14086
14275
|
buf.write("WAVE", 8, "ascii");
|
|
14087
14276
|
buf.write("fmt ", 12, "ascii");
|
|
14088
14277
|
buf.writeUInt32LE(16, 16);
|
|
@@ -14093,15 +14282,25 @@ function writeWav(path, samples, sampleRate, channels = 1, float = false) {
|
|
|
14093
14282
|
buf.writeUInt16LE(channels * bytesPerSample, 32);
|
|
14094
14283
|
buf.writeUInt16LE(float ? 32 : 16, 34);
|
|
14095
14284
|
buf.write("data", 36, "ascii");
|
|
14096
|
-
buf.writeUInt32LE(bytes, 40);
|
|
14285
|
+
buf.writeUInt32LE(Math.min(4294967295, bytes), 40);
|
|
14286
|
+
return buf;
|
|
14287
|
+
}
|
|
14288
|
+
function encodeSamples(samples, float) {
|
|
14289
|
+
const n = samples.length;
|
|
14290
|
+
const buf = Buffer.alloc(n * (float ? 4 : 2));
|
|
14097
14291
|
for (let i = 0; i < n; i++) {
|
|
14098
14292
|
if (float) {
|
|
14099
|
-
buf.writeFloatLE(samples[i] ?? 0,
|
|
14293
|
+
buf.writeFloatLE(samples[i] ?? 0, i * 4);
|
|
14100
14294
|
continue;
|
|
14101
14295
|
}
|
|
14102
14296
|
const v = Math.max(-1, Math.min(1, samples[i] ?? 0));
|
|
14103
|
-
buf.writeInt16LE(Math.round(v * 32767),
|
|
14297
|
+
buf.writeInt16LE(Math.round(v * 32767), i * 2);
|
|
14104
14298
|
}
|
|
14299
|
+
return buf;
|
|
14300
|
+
}
|
|
14301
|
+
function writeWav(path, samples, sampleRate, channels = 1, float = false) {
|
|
14302
|
+
const body = encodeSamples(samples, float);
|
|
14303
|
+
const buf = Buffer.concat([wavHeader(body.length, sampleRate, channels, float), body]);
|
|
14105
14304
|
writeFileSync5(path, buf);
|
|
14106
14305
|
}
|
|
14107
14306
|
function deinterleave(samples, channels) {
|
|
@@ -14127,38 +14326,153 @@ function interleave(planes) {
|
|
|
14127
14326
|
return out;
|
|
14128
14327
|
}
|
|
14129
14328
|
var TRANSFER_BYTES = 8 * 1024 * 1024;
|
|
14130
|
-
function applyEnvelopeToPlanes(planes, sampleRate, gainAt) {
|
|
14329
|
+
function applyEnvelopeToPlanes(planes, sampleRate, gainAt, startFrame = 0) {
|
|
14131
14330
|
const frames = planes[0]?.length ?? 0;
|
|
14132
14331
|
for (let frame = 0; frame < frames; frame += 1) {
|
|
14133
|
-
const gain = gainAt(frame / sampleRate);
|
|
14332
|
+
const gain = gainAt((startFrame + frame) / sampleRate);
|
|
14134
14333
|
for (const plane of planes) plane[frame] = (plane[frame] ?? 0) * gain;
|
|
14135
14334
|
}
|
|
14136
14335
|
}
|
|
14336
|
+
var DEFAULT_STREAMING_LIMITS = {
|
|
14337
|
+
aboveBytes: 512 * 1024 * 1024,
|
|
14338
|
+
segmentSeconds: 300,
|
|
14339
|
+
prerollSeconds: 10,
|
|
14340
|
+
maxPrerollSeconds: 300,
|
|
14341
|
+
postrollSeconds: 1
|
|
14342
|
+
};
|
|
14343
|
+
var RENDER_QUANTUM = 128;
|
|
14344
|
+
function planAudioFxSegments(totalFrames, sampleRate, limits) {
|
|
14345
|
+
const step = Math.max(1, Math.round(limits.segmentSeconds * sampleRate));
|
|
14346
|
+
const pre = Math.round(limits.prerollSeconds * sampleRate);
|
|
14347
|
+
const post = Math.round(limits.postrollSeconds * sampleRate);
|
|
14348
|
+
const segments = [];
|
|
14349
|
+
for (let keepFrom = 0; keepFrom < totalFrames; keepFrom += step) {
|
|
14350
|
+
const keepTo = Math.min(totalFrames, keepFrom + step);
|
|
14351
|
+
segments.push({
|
|
14352
|
+
keepFrom,
|
|
14353
|
+
keepTo,
|
|
14354
|
+
readFrom: Math.max(0, Math.floor((keepFrom - pre) / RENDER_QUANTUM) * RENDER_QUANTUM),
|
|
14355
|
+
readTo: Math.min(totalFrames, keepTo + post),
|
|
14356
|
+
last: keepTo === totalFrames
|
|
14357
|
+
});
|
|
14358
|
+
}
|
|
14359
|
+
return segments;
|
|
14360
|
+
}
|
|
14361
|
+
function readFrames(fd, header, from, to) {
|
|
14362
|
+
const frameBytes = header.channels * header.bits / 8;
|
|
14363
|
+
const data = Buffer.alloc((to - from) * frameBytes);
|
|
14364
|
+
for (let filled = 0; filled < data.length; ) {
|
|
14365
|
+
const n = readSync(
|
|
14366
|
+
fd,
|
|
14367
|
+
data,
|
|
14368
|
+
filled,
|
|
14369
|
+
data.length - filled,
|
|
14370
|
+
header.dataOffset + from * frameBytes + filled
|
|
14371
|
+
);
|
|
14372
|
+
if (n === 0) break;
|
|
14373
|
+
filled += n;
|
|
14374
|
+
}
|
|
14375
|
+
return decodeSamples(data, header.format, header.bits, "streamed input");
|
|
14376
|
+
}
|
|
14377
|
+
function chainPrerollSeconds(chain, limits, trackId) {
|
|
14378
|
+
const { seconds, capped } = windowPrerollSeconds(
|
|
14379
|
+
chain,
|
|
14380
|
+
limits.prerollSeconds,
|
|
14381
|
+
limits.maxPrerollSeconds
|
|
14382
|
+
);
|
|
14383
|
+
if (capped) {
|
|
14384
|
+
console.warn(
|
|
14385
|
+
`Audio FX for track ${trackId}: the chain remembers longer than the ${limits.maxPrerollSeconds}s pre-roll cap, so its oldest repeats are cut at window seams`
|
|
14386
|
+
);
|
|
14387
|
+
}
|
|
14388
|
+
return seconds;
|
|
14389
|
+
}
|
|
14390
|
+
async function renderInSegments(openPage, inputWav, header, chain, outputWav, options, limits) {
|
|
14391
|
+
const { sampleRate, channels, float } = header;
|
|
14392
|
+
const totalFrames = Math.floor(header.dataBytes / (channels * header.bits / 8));
|
|
14393
|
+
const gainAt = envelopeWalkerFor(options.envelope);
|
|
14394
|
+
const prerollSeconds = chainPrerollSeconds(chain, limits, options.trackId);
|
|
14395
|
+
const inFd = openSync3(inputWav, "r");
|
|
14396
|
+
let outFd;
|
|
14397
|
+
try {
|
|
14398
|
+
outFd = openSync3(outputWav, "w");
|
|
14399
|
+
writeSync2(outFd, wavHeader(0, sampleRate, channels, float));
|
|
14400
|
+
let written = 0;
|
|
14401
|
+
for (const seg of planAudioFxSegments(totalFrames, sampleRate, {
|
|
14402
|
+
...limits,
|
|
14403
|
+
prerollSeconds
|
|
14404
|
+
})) {
|
|
14405
|
+
if (options.signal?.aborted) {
|
|
14406
|
+
throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
|
|
14407
|
+
}
|
|
14408
|
+
const planes = deinterleave(readFrames(inFd, header, seg.readFrom, seg.readTo), channels);
|
|
14409
|
+
const page = await openPage();
|
|
14410
|
+
let outPlanes;
|
|
14411
|
+
try {
|
|
14412
|
+
await sendPlanesToPage(page, planes);
|
|
14413
|
+
outPlanes = await readPlanesFromPage(
|
|
14414
|
+
page,
|
|
14415
|
+
await renderPlanesInPage(page, sampleRate, chain, "", seg.readFrom / sampleRate)
|
|
14416
|
+
);
|
|
14417
|
+
} finally {
|
|
14418
|
+
await page.close().catch(() => void 0);
|
|
14419
|
+
}
|
|
14420
|
+
const rendered = outPlanes[0]?.length ?? 0;
|
|
14421
|
+
if (rendered === 0) {
|
|
14422
|
+
throw new AudioFxRenderError(`Audio FX produced no samples for track ${options.trackId}`);
|
|
14423
|
+
}
|
|
14424
|
+
const skip = seg.keepFrom - seg.readFrom;
|
|
14425
|
+
const kept = outPlanes.map(
|
|
14426
|
+
(plane) => plane.subarray(skip, seg.last ? rendered : seg.keepTo - seg.readFrom)
|
|
14427
|
+
);
|
|
14428
|
+
if (gainAt) applyEnvelopeToPlanes(kept, sampleRate, gainAt, seg.keepFrom);
|
|
14429
|
+
const bytes = encodeSamples(interleave(kept), float);
|
|
14430
|
+
writeFileSync5(outFd, bytes);
|
|
14431
|
+
written += bytes.length;
|
|
14432
|
+
}
|
|
14433
|
+
writeSync2(outFd, wavHeader(written, sampleRate, channels, float), 0, 44, 0);
|
|
14434
|
+
} finally {
|
|
14435
|
+
closeSync3(inFd);
|
|
14436
|
+
if (outFd !== void 0) closeSync3(outFd);
|
|
14437
|
+
}
|
|
14438
|
+
return gainAt !== null;
|
|
14439
|
+
}
|
|
14137
14440
|
async function applyAudioFxChain(inputWav, chain, outputWav, options) {
|
|
14138
14441
|
if (enabledAudioFxNodes(chain).length === 0) return { path: inputWav, envelopeBaked: false };
|
|
14139
14442
|
if (!existsSync9(inputWav)) {
|
|
14140
14443
|
throw new AudioFxRenderError(`Audio FX input is missing: ${inputWav}`);
|
|
14141
14444
|
}
|
|
14142
|
-
const
|
|
14143
|
-
const
|
|
14445
|
+
const limits = { ...DEFAULT_STREAMING_LIMITS, ...options.streaming };
|
|
14446
|
+
const header = options.automation ? null : readWavHeader(inputWav);
|
|
14447
|
+
const streamed = header !== null && header.dataBytes > limits.aboveBytes;
|
|
14448
|
+
if (streamed) {
|
|
14449
|
+
return withFxBrowser(options, async (openPage) => ({
|
|
14450
|
+
path: outputWav,
|
|
14451
|
+
envelopeBaked: await renderInSegments(
|
|
14452
|
+
openPage,
|
|
14453
|
+
inputWav,
|
|
14454
|
+
header,
|
|
14455
|
+
chain,
|
|
14456
|
+
outputWav,
|
|
14457
|
+
options,
|
|
14458
|
+
limits
|
|
14459
|
+
)
|
|
14460
|
+
}));
|
|
14461
|
+
}
|
|
14462
|
+
if (statSync7(inputWav).size > MAX_WHOLE_FILE_BYTES) {
|
|
14463
|
+
const why = options.automation ? "parameter automation is not supported on inputs over 2 GiB" : "the WAV header of an input over 2 GiB could not be read";
|
|
14464
|
+
throw new AudioFxRenderError(`Audio FX for track ${options.trackId}: ${why} (${inputWav})`);
|
|
14465
|
+
}
|
|
14466
|
+
const input = readWav(inputWav);
|
|
14467
|
+
const planes = deinterleave(input.samples, input.channels);
|
|
14144
14468
|
if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
try {
|
|
14148
|
-
if (options.signal?.aborted) {
|
|
14149
|
-
throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
|
|
14150
|
-
}
|
|
14151
|
-
lease = await acquireBrowser(["--no-sandbox", "--autoplay-policy=no-user-gesture-required"]);
|
|
14152
|
-
const page = await lease.browser.newPage();
|
|
14469
|
+
return withFxBrowser(options, async (openPage) => {
|
|
14470
|
+
const page = await openPage();
|
|
14153
14471
|
try {
|
|
14154
|
-
const hostPage = join12(hostDir, "audio-fx.html");
|
|
14155
|
-
writeFileSync5(hostPage, "<!doctype html><meta charset=utf-8><title>audio fx</title>");
|
|
14156
|
-
await page.goto(pathToFileURL(hostPage).href, { waitUntil: "domcontentloaded" });
|
|
14157
|
-
await page.addScriptTag({ content: getAudioFxRuntimeScript() });
|
|
14158
14472
|
await sendPlanesToPage(page, planes);
|
|
14159
14473
|
const outLengths = await renderPlanesInPage(
|
|
14160
14474
|
page,
|
|
14161
|
-
sampleRate,
|
|
14475
|
+
input.sampleRate,
|
|
14162
14476
|
chain,
|
|
14163
14477
|
options.automation ? serializeAutomation(options.automation) : ""
|
|
14164
14478
|
);
|
|
@@ -14167,12 +14481,24 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
|
|
|
14167
14481
|
throw new AudioFxRenderError(`Audio FX produced no samples for track ${options.trackId}`);
|
|
14168
14482
|
}
|
|
14169
14483
|
const gainAt = envelopeWalkerFor(options.envelope);
|
|
14170
|
-
if (gainAt) applyEnvelopeToPlanes(outPlanes, sampleRate, gainAt);
|
|
14171
|
-
writeWav(outputWav, interleave(outPlanes), sampleRate, outPlanes.length, float);
|
|
14484
|
+
if (gainAt) applyEnvelopeToPlanes(outPlanes, input.sampleRate, gainAt);
|
|
14485
|
+
writeWav(outputWav, interleave(outPlanes), input.sampleRate, outPlanes.length, input.float);
|
|
14172
14486
|
return { path: outputWav, envelopeBaked: gainAt !== null };
|
|
14173
14487
|
} finally {
|
|
14174
14488
|
await page.close().catch(() => void 0);
|
|
14175
14489
|
}
|
|
14490
|
+
});
|
|
14491
|
+
}
|
|
14492
|
+
async function withFxBrowser(options, body) {
|
|
14493
|
+
const hostDir = mkdtempSync5(join12(tmpdir3(), "hf-fx-host-"));
|
|
14494
|
+
let lease = null;
|
|
14495
|
+
try {
|
|
14496
|
+
if (options.signal?.aborted) {
|
|
14497
|
+
throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
|
|
14498
|
+
}
|
|
14499
|
+
lease = await acquireBrowser(["--no-sandbox", "--autoplay-policy=no-user-gesture-required"]);
|
|
14500
|
+
const { browser } = lease;
|
|
14501
|
+
return await body(() => openFxPage(browser, hostDir));
|
|
14176
14502
|
} catch (err) {
|
|
14177
14503
|
if (err instanceof AudioFxRenderError) throw err;
|
|
14178
14504
|
throw new AudioFxRenderError(
|
|
@@ -14183,6 +14509,21 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
|
|
|
14183
14509
|
await lease?.release().catch(() => void 0);
|
|
14184
14510
|
}
|
|
14185
14511
|
}
|
|
14512
|
+
async function openFxPage(browser, hostDir) {
|
|
14513
|
+
const page = await browser.newPage();
|
|
14514
|
+
try {
|
|
14515
|
+
const hostPage = join12(hostDir, "audio-fx.html");
|
|
14516
|
+
writeFileSync5(hostPage, "<!doctype html><meta charset=utf-8><title>audio fx</title>");
|
|
14517
|
+
await page.goto(pathToFileURL(hostPage).href, {
|
|
14518
|
+
waitUntil: "domcontentloaded"
|
|
14519
|
+
});
|
|
14520
|
+
await page.addScriptTag({ content: getAudioFxRuntimeScript() });
|
|
14521
|
+
return page;
|
|
14522
|
+
} catch (err) {
|
|
14523
|
+
await page.close().catch(() => void 0);
|
|
14524
|
+
throw err;
|
|
14525
|
+
}
|
|
14526
|
+
}
|
|
14186
14527
|
async function sendPlanesToPage(page, planes) {
|
|
14187
14528
|
await page.evaluate((count) => {
|
|
14188
14529
|
window.__HF_FX_IO = {
|
|
@@ -14207,26 +14548,26 @@ async function sendPlanesToPage(page, planes) {
|
|
|
14207
14548
|
}
|
|
14208
14549
|
}
|
|
14209
14550
|
}
|
|
14210
|
-
async function renderPlanesInPage(page, sampleRate, chain, automationJson) {
|
|
14551
|
+
async function renderPlanesInPage(page, sampleRate, chain, automationJson, elapsed = 0) {
|
|
14211
14552
|
return await page.evaluate(
|
|
14212
|
-
async ([rate, chainJson, automation]) => {
|
|
14553
|
+
async ([rate, chainJson, automation, at]) => {
|
|
14213
14554
|
const w = window;
|
|
14214
14555
|
const io = w.__HF_FX_IO;
|
|
14215
14556
|
if (!w.__HF_AUDIO_FX || !io) throw new Error("audio FX runtime failed to load");
|
|
14216
14557
|
const inPlanes = io.in.map((chunks) => {
|
|
14217
14558
|
const bytes = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
14218
|
-
let
|
|
14559
|
+
let at2 = 0;
|
|
14219
14560
|
for (const chunk of chunks) {
|
|
14220
|
-
bytes.set(chunk,
|
|
14221
|
-
|
|
14561
|
+
bytes.set(chunk, at2);
|
|
14562
|
+
at2 += chunk.length;
|
|
14222
14563
|
}
|
|
14223
14564
|
return new Float32Array(bytes.buffer);
|
|
14224
14565
|
});
|
|
14225
14566
|
io.in = [];
|
|
14226
|
-
io.out = await w.__HF_AUDIO_FX.render(inPlanes, rate, chainJson, automation || void 0);
|
|
14567
|
+
io.out = await w.__HF_AUDIO_FX.render(inPlanes, rate, chainJson, automation || void 0, at);
|
|
14227
14568
|
return io.out.map((plane) => plane.length);
|
|
14228
14569
|
},
|
|
14229
|
-
[sampleRate, JSON.stringify(chain), automationJson]
|
|
14570
|
+
[sampleRate, JSON.stringify(chain), automationJson, elapsed]
|
|
14230
14571
|
);
|
|
14231
14572
|
}
|
|
14232
14573
|
async function readPlaneFromPage(page, index, byteLength) {
|
|
@@ -14541,6 +14882,9 @@ function volumeLaneKeyframes(automation, trackStart, duration) {
|
|
|
14541
14882
|
if (duration > last.t) push(duration, last.v);
|
|
14542
14883
|
return out;
|
|
14543
14884
|
}
|
|
14885
|
+
function parseMasterFxChain(html) {
|
|
14886
|
+
return readMasterFxChain(parseHTML(unwrapTemplate(html)).document);
|
|
14887
|
+
}
|
|
14544
14888
|
function parseAudioElements(html) {
|
|
14545
14889
|
const elements = [];
|
|
14546
14890
|
const { document: document2 } = parseHTML(unwrapTemplate(html));
|
|
@@ -14691,7 +15035,7 @@ async function generateSilence(outputPath, duration, signal, config) {
|
|
|
14691
15035
|
failure
|
|
14692
15036
|
};
|
|
14693
15037
|
}
|
|
14694
|
-
async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config) {
|
|
15038
|
+
async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config, floatWav = false) {
|
|
14695
15039
|
const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
14696
15040
|
const masterOutputGain = config?.audioGain ?? DEFAULT_CONFIG.audioGain;
|
|
14697
15041
|
if (tracks.length === 0) {
|
|
@@ -14724,11 +15068,11 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
14724
15068
|
tracks.forEach((track) => inputs.push("-i", track.srcPath));
|
|
14725
15069
|
const scriptDir = mkdtempSync6(join13(outputDir, ".filter-complex-"));
|
|
14726
15070
|
const scriptPath = join13(scriptDir, "graph.txt");
|
|
14727
|
-
const fd =
|
|
15071
|
+
const fd = openSync4(scriptPath, "wx", 384);
|
|
14728
15072
|
try {
|
|
14729
15073
|
writeFileSync6(fd, buildFilterComplex(ignoreAutomation));
|
|
14730
15074
|
} finally {
|
|
14731
|
-
|
|
15075
|
+
closeSync4(fd);
|
|
14732
15076
|
}
|
|
14733
15077
|
const args = [
|
|
14734
15078
|
...inputs,
|
|
@@ -14736,10 +15080,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
14736
15080
|
scriptPath,
|
|
14737
15081
|
"-map",
|
|
14738
15082
|
"[out]",
|
|
14739
|
-
"-acodec",
|
|
14740
|
-
"aac",
|
|
14741
|
-
"-b:a",
|
|
14742
|
-
"192k",
|
|
15083
|
+
...floatWav ? ["-acodec", "pcm_f32le", "-ar", "48000"] : ["-acodec", "aac", "-b:a", "192k"],
|
|
14743
15084
|
"-t",
|
|
14744
15085
|
String(totalDuration),
|
|
14745
15086
|
"-y",
|
|
@@ -14829,11 +15170,11 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
|
|
|
14829
15170
|
const filterComplex = [...inputFilters, mixFilter].join(";");
|
|
14830
15171
|
const scriptDir = mkdtempSync6(join13(outputDir, ".group-filter-complex-"));
|
|
14831
15172
|
const scriptPath = join13(scriptDir, "graph.txt");
|
|
14832
|
-
const fd =
|
|
15173
|
+
const fd = openSync4(scriptPath, "wx", 384);
|
|
14833
15174
|
try {
|
|
14834
15175
|
writeFileSync6(fd, filterComplex);
|
|
14835
15176
|
} finally {
|
|
14836
|
-
|
|
15177
|
+
closeSync4(fd);
|
|
14837
15178
|
}
|
|
14838
15179
|
try {
|
|
14839
15180
|
const inputs = [];
|
|
@@ -14891,6 +15232,42 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
|
|
|
14891
15232
|
return { success: false, error: formatFfmpegError(result.exitCode, result.stderr) };
|
|
14892
15233
|
return { success: true, degradedAutomation };
|
|
14893
15234
|
}
|
|
15235
|
+
async function mixThroughMasterBus(tracks, masterChain, outputPath, workDir, totalDuration, signal, config) {
|
|
15236
|
+
const sumPath = join13(workDir, "master-sum.wav");
|
|
15237
|
+
const sum = await mixAudioTracks(tracks, sumPath, totalDuration, signal, config, true);
|
|
15238
|
+
if (!sum.success) return { ...sum, outputPath };
|
|
15239
|
+
const fx = await applyAudioFxChain(sumPath, masterChain, join13(workDir, "master-fx.wav"), {
|
|
15240
|
+
trackId: "master",
|
|
15241
|
+
signal
|
|
15242
|
+
});
|
|
15243
|
+
const encoded = await runFfmpeg(
|
|
15244
|
+
[
|
|
15245
|
+
"-i",
|
|
15246
|
+
fx.path,
|
|
15247
|
+
"-t",
|
|
15248
|
+
String(totalDuration),
|
|
15249
|
+
"-acodec",
|
|
15250
|
+
"aac",
|
|
15251
|
+
"-b:a",
|
|
15252
|
+
"192k",
|
|
15253
|
+
"-y",
|
|
15254
|
+
outputPath
|
|
15255
|
+
],
|
|
15256
|
+
{ signal, timeout: config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout }
|
|
15257
|
+
);
|
|
15258
|
+
if (!encoded.success) {
|
|
15259
|
+
const failure = ffmpegFailure("mix", encoded);
|
|
15260
|
+
return {
|
|
15261
|
+
success: false,
|
|
15262
|
+
outputPath,
|
|
15263
|
+
durationMs: sum.durationMs + encoded.durationMs,
|
|
15264
|
+
tracksProcessed: 0,
|
|
15265
|
+
error: failure.detail,
|
|
15266
|
+
failures: [failure]
|
|
15267
|
+
};
|
|
15268
|
+
}
|
|
15269
|
+
return { ...sum, outputPath, durationMs: sum.durationMs + encoded.durationMs };
|
|
15270
|
+
}
|
|
14894
15271
|
async function processCompositionAudio(elements, baseDir, workDir, outputPath, totalDuration, signal, config, compiledDir) {
|
|
14895
15272
|
const startMs = Date.now();
|
|
14896
15273
|
const tracks = [];
|
|
@@ -15121,11 +15498,14 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15121
15498
|
}
|
|
15122
15499
|
throw err;
|
|
15123
15500
|
});
|
|
15124
|
-
const
|
|
15501
|
+
const removeWorkDir = () => {
|
|
15125
15502
|
try {
|
|
15126
15503
|
rmSync7(workDir, { recursive: true, force: true });
|
|
15127
15504
|
} catch {
|
|
15128
15505
|
}
|
|
15506
|
+
};
|
|
15507
|
+
const bail = () => {
|
|
15508
|
+
removeWorkDir();
|
|
15129
15509
|
return {
|
|
15130
15510
|
success: false,
|
|
15131
15511
|
outputPath,
|
|
@@ -15138,6 +15518,25 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15138
15518
|
};
|
|
15139
15519
|
};
|
|
15140
15520
|
if (failures.length > 0) return bail();
|
|
15521
|
+
let masterChain;
|
|
15522
|
+
const masterFxJson = config?.masterFxChain;
|
|
15523
|
+
if (masterFxJson) {
|
|
15524
|
+
try {
|
|
15525
|
+
masterChain = parseAudioFxChain(masterFxJson);
|
|
15526
|
+
} catch (err) {
|
|
15527
|
+
failures.push({
|
|
15528
|
+
stage: "mix",
|
|
15529
|
+
reason: "internal",
|
|
15530
|
+
owner: "user",
|
|
15531
|
+
retryable: false,
|
|
15532
|
+
elementId: "master",
|
|
15533
|
+
detail: boundedDetail(
|
|
15534
|
+
`Master bus chain is unreadable: ${err instanceof Error ? err.message : String(err)}`
|
|
15535
|
+
)
|
|
15536
|
+
});
|
|
15537
|
+
return bail();
|
|
15538
|
+
}
|
|
15539
|
+
}
|
|
15141
15540
|
const groupsDegradedAutomation = [];
|
|
15142
15541
|
let groupIndex = -1;
|
|
15143
15542
|
for (const [groupId, memberTracks] of groupTracks) {
|
|
@@ -15214,7 +15613,10 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15214
15613
|
...bakedEnvelope || !laneKeyframes?.length ? {} : { volumeKeyframes: laneKeyframes }
|
|
15215
15614
|
});
|
|
15216
15615
|
} catch (err) {
|
|
15217
|
-
if (err instanceof AudioFxRenderError)
|
|
15616
|
+
if (err instanceof AudioFxRenderError) {
|
|
15617
|
+
removeWorkDir();
|
|
15618
|
+
throw err;
|
|
15619
|
+
}
|
|
15218
15620
|
failures.push({
|
|
15219
15621
|
stage: "mix",
|
|
15220
15622
|
reason: "internal",
|
|
@@ -15228,10 +15630,19 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15228
15630
|
}
|
|
15229
15631
|
}
|
|
15230
15632
|
if (failures.length > 0) return bail();
|
|
15231
|
-
|
|
15633
|
+
let mixResult;
|
|
15232
15634
|
try {
|
|
15233
|
-
|
|
15234
|
-
|
|
15635
|
+
mixResult = masterChain && tracks.length > 0 ? await mixThroughMasterBus(
|
|
15636
|
+
tracks,
|
|
15637
|
+
masterChain,
|
|
15638
|
+
outputPath,
|
|
15639
|
+
workDir,
|
|
15640
|
+
totalDuration,
|
|
15641
|
+
signal,
|
|
15642
|
+
config
|
|
15643
|
+
) : await mixAudioTracks(tracks, outputPath, totalDuration, signal, config);
|
|
15644
|
+
} finally {
|
|
15645
|
+
removeWorkDir();
|
|
15235
15646
|
}
|
|
15236
15647
|
const degradedNote = groupsDegradedAutomation.length > 0 ? `Volume automation exceeded this ffmpeg build's expression limits in group(s) ${groupsDegradedAutomation.join(", ")}; rendered at base volume` : void 0;
|
|
15237
15648
|
return {
|
|
@@ -15974,7 +16385,7 @@ function getSystemResources() {
|
|
|
15974
16385
|
// ../engine/src/services/fileServer.ts
|
|
15975
16386
|
import { Hono } from "hono";
|
|
15976
16387
|
import { serve } from "@hono/node-server";
|
|
15977
|
-
import { readFileSync as readFileSync7, openSync as
|
|
16388
|
+
import { readFileSync as readFileSync7, openSync as openSync5, fstatSync, closeSync as closeSync5, statSync as statSync8, constants } from "fs";
|
|
15978
16389
|
import { join as join16, extname as extname3 } from "path";
|
|
15979
16390
|
var MIME_TYPES = {
|
|
15980
16391
|
".html": "text/html; charset=utf-8",
|
|
@@ -16007,18 +16418,18 @@ var MIME_TYPES = {
|
|
|
16007
16418
|
function readRegularFile(filePath) {
|
|
16008
16419
|
let fd;
|
|
16009
16420
|
try {
|
|
16010
|
-
fd =
|
|
16421
|
+
fd = openSync5(filePath, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
16011
16422
|
} catch (error) {
|
|
16012
16423
|
if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
|
|
16013
16424
|
return null;
|
|
16014
|
-
if (!
|
|
16425
|
+
if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
|
|
16015
16426
|
throw error;
|
|
16016
16427
|
}
|
|
16017
16428
|
try {
|
|
16018
16429
|
if (!fstatSync(fd).isFile()) return null;
|
|
16019
16430
|
return readFileSync7(fd);
|
|
16020
16431
|
} finally {
|
|
16021
|
-
|
|
16432
|
+
closeSync5(fd);
|
|
16022
16433
|
}
|
|
16023
16434
|
}
|
|
16024
16435
|
function resolveContainedPaths(projectDir, compiledDir, relativePath) {
|
|
@@ -16440,6 +16851,7 @@ export {
|
|
|
16440
16851
|
shouldClampToScreenshotForConcreteGpu,
|
|
16441
16852
|
applyConcreteGpuScreenshotClamp,
|
|
16442
16853
|
SDR_RGB_TO_BT709_FILTER,
|
|
16854
|
+
SDR_RGB_TO_TAGGED_BT709_FILTER,
|
|
16443
16855
|
BrowserLeasePool,
|
|
16444
16856
|
setHostHandlesSigint,
|
|
16445
16857
|
resolveHeadlessShellPath,
|
|
@@ -16607,6 +17019,7 @@ export {
|
|
|
16607
17019
|
isNotMediaPayload,
|
|
16608
17020
|
assertMediaPayload,
|
|
16609
17021
|
MIXED_AUDIO_FILENAME,
|
|
17022
|
+
parseMasterFxChain,
|
|
16610
17023
|
parseAudioElements,
|
|
16611
17024
|
processCompositionAudio,
|
|
16612
17025
|
psnrDb,
|