@bendyline/squisq 2.11.7 → 2.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/{Doc-BkxT7G80.d.ts → Doc-DmElSKmg.d.ts} +219 -1
  2. package/dist/{ImageEditDoc-C8ZB54Vv.d.ts → ImageEditDoc-Dm_BETxC.d.ts} +1 -1
  3. package/dist/{chunk-EF353KGH.js → chunk-2PGC4JDT.js} +1 -1
  4. package/dist/{chunk-YGGYYBJS.js → chunk-B5STNW6C.js} +2 -2
  5. package/dist/{chunk-IYZTF4VO.js → chunk-CCRBHFNO.js} +3 -3
  6. package/dist/{chunk-VU4T74LI.js → chunk-DTM4FG7S.js} +1 -1
  7. package/dist/chunk-JSGIWW35.js +70 -0
  8. package/dist/chunk-L7QZORLB.js +230 -0
  9. package/dist/chunk-NEWNNLWN.js +64 -0
  10. package/dist/{chunk-6ECMNUIP.js → chunk-OU5FYBOB.js} +170 -4
  11. package/dist/{chunk-Y4D6TDZT.js → chunk-PCCQLD3G.js} +2 -0
  12. package/dist/{chunk-SKKBRLLM.js → chunk-QDA7NXDO.js} +7 -42
  13. package/dist/{chunk-3YNFMFNK.js → chunk-QOVINAB2.js} +18 -9
  14. package/dist/{chunk-SOIV7KDO.js → chunk-VRTXPGP7.js} +256 -26
  15. package/dist/{chunk-XJXBI3N5.js → chunk-XZVBR5BP.js} +1 -1
  16. package/dist/{chunk-GAZKTT4R.js → chunk-YNO6JV36.js} +96 -33
  17. package/dist/doc/index.d.ts +10 -6
  18. package/dist/doc/index.js +23 -21
  19. package/dist/fence/index.d.ts +1 -1
  20. package/dist/generate/index.d.ts +1 -1
  21. package/dist/imageEdit/index.d.ts +47 -4
  22. package/dist/imageEdit/index.js +5 -1
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +52 -41
  25. package/dist/jsonForm/index.d.ts +1 -1
  26. package/dist/jsonForm/index.js +6 -4
  27. package/dist/markdown/index.js +3 -3
  28. package/dist/{materializePageSection-Dp2aHFbc.d.ts → materializePageSection-BfttC9Zj.d.ts} +6 -3
  29. package/dist/mediaEdit/index.d.ts +467 -0
  30. package/dist/mediaEdit/index.js +990 -0
  31. package/dist/narration/index.d.ts +20 -12
  32. package/dist/narration/index.js +7 -5
  33. package/dist/schemas/index.d.ts +4 -4
  34. package/dist/schemas/index.js +10 -4
  35. package/dist/{themeLibrary-BbSjnGmd.d.ts → themeLibrary-ChnTImby.d.ts} +1 -1
  36. package/dist/timing/index.d.ts +1 -1
  37. package/dist/transform/index.d.ts +2 -2
  38. package/dist/transform/index.js +7 -6
  39. package/package.json +6 -1
  40. package/dist/{chunk-CW24CH7I.js → chunk-HUXK7EOT.js} +3 -3
@@ -0,0 +1,467 @@
1
+ import { ah as MediaCut, O as Doc, af as MediaClip, ch as MediaFxOpId, ci as MediaFxChain } from '../Doc-DmElSKmg.js';
2
+ export { cj as MEDIA_EDIT_PARAMS, ck as MEDIA_FX_ORDER, cl as MEDIA_FX_SPECS, ag as MediaCrop, cm as MediaEditParamKey, cn as MediaEditParamValues, ai as MediaEdits, co as MediaFxOp, cp as formatMediaNumber, cq as hasRenderableFx, cr as mediaEditHtmlAttribute, cs as mediaEditValuesFromAttributes, ct as mediaEditValuesFromParams, cu as mediaRenderKey, cv as normalizeMediaCrop, cw as normalizeMediaCuts, cx as parseMediaCrop, cy as parseMediaCuts, cz as parseMediaEdits, cA as parseMediaFx, cB as serializeMediaCrop, cC as serializeMediaCuts, cD as serializeMediaEdits, cE as serializeMediaFx } from '../Doc-DmElSKmg.js';
3
+ import '../types-Be6sBUBx.js';
4
+
5
+ /**
6
+ * Source time ↔ played time for a media clip with a trim window and cuts.
7
+ *
8
+ * A clip plays its source from `clipStart` to `clipEnd` (the trim window)
9
+ * with every cut range skipped. "Played time" is seconds since the clip
10
+ * started playing: 0 at `clipStart`, advancing through the kept ranges back
11
+ * to back. Everything time-indexed against a source file (narration word
12
+ * timings, block ranges, a companion clip's cuts) goes through this map, so
13
+ * one function decides what a cut does to time.
14
+ *
15
+ * Pure and dependency-free.
16
+ */
17
+
18
+ interface MediaTimeMapInput {
19
+ /** Source in-point in seconds. Default 0. */
20
+ clipStart?: number;
21
+ /** Source out-point in seconds. Default: the source's end (unbounded). */
22
+ clipEnd?: number;
23
+ /** Removed source ranges (any order; normalized here). */
24
+ cuts?: readonly MediaCut[];
25
+ /** Known source length; caps an unbounded trim window. */
26
+ sourceDuration?: number;
27
+ }
28
+ interface MediaTimeMap {
29
+ /** Trim window start (source seconds). */
30
+ readonly windowStart: number;
31
+ /** Trim window end (source seconds); `Infinity` when unknown. */
32
+ readonly windowEnd: number;
33
+ /** Cuts clamped into the window, normalized. */
34
+ readonly cuts: readonly MediaCut[];
35
+ /** True when at least one cut falls inside the window. */
36
+ readonly hasCuts: boolean;
37
+ /** Played length; `Infinity` when the window end is unknown. */
38
+ readonly playedDuration: number;
39
+ /** The kept source ranges, in order (the last may end at `Infinity`). */
40
+ keptRanges(): MediaCut[];
41
+ /** Played time of a source instant, or null outside the window or inside a cut. */
42
+ sourceToPlayed(sourceSec: number): number | null;
43
+ /**
44
+ * Like {@link sourceToPlayed} but total: an instant inside a cut maps to the
45
+ * cut's boundary, before the window to 0, after it to the played end. Use
46
+ * for range endpoints (a block that ends inside a cut ends at the cut).
47
+ */
48
+ snapSourceToPlayed(sourceSec: number): number;
49
+ /** Source instant at a played time (clamped into the played range). */
50
+ playedToSource(playedSec: number): number;
51
+ }
52
+ declare function createMediaTimeMap(input: MediaTimeMapInput): MediaTimeMap;
53
+
54
+ /**
55
+ * Render store — where derived (processed) audio lives, and how to find it.
56
+ *
57
+ * A signal recipe (`fx`) is rendered into an audio-only file that is
58
+ * sample-aligned with the source's audio track: same start, same length,
59
+ * never cut. Renders live in a hidden `.mediaEdits/` folder beside the rest
60
+ * of the document's media, so they travel with the document (copy, zip, git)
61
+ * and every surface — player, export, CLI — can reuse them. They are a cache:
62
+ * the recipe in the markdown is the source of truth, and any render can be
63
+ * deleted and regenerated.
64
+ *
65
+ * Layout (paths relative to the media root a `MediaProvider` resolves):
66
+ *
67
+ * .mediaEdits/<stem>.<key>.<ext> the rendered audio (webm/Opus or wav)
68
+ * .mediaEdits/<stem>.<key>.json its manifest
69
+ *
70
+ * `<key>` is {@link mediaRenderKey}(src, fx), so availability is readable from
71
+ * a directory listing alone; manifests are read only for staleness and GC.
72
+ *
73
+ * Pure and dependency-free.
74
+ */
75
+
76
+ /** Folder (under the media root) holding renders and manifests. */
77
+ declare const MEDIA_RENDER_DIR = ".mediaEdits";
78
+ /** Manifest schema version. */
79
+ declare const MEDIA_RENDER_MANIFEST_VERSION = 1;
80
+ /** Renders unreferenced for at least this long are eligible for GC. */
81
+ declare const MEDIA_RENDER_GC_AGE_DAYS = 7;
82
+ interface MediaRenderAnalysis {
83
+ /** Integrated loudness before the loudness stage, LUFS. */
84
+ integratedLufs?: number;
85
+ /** Gain the loudness stage applied, dB. */
86
+ appliedGainDb?: number;
87
+ /** Breaths the de-breath stage attenuated. */
88
+ breathCount?: number;
89
+ }
90
+ interface MediaRenderManifest {
91
+ version: typeof MEDIA_RENDER_MANIFEST_VERSION;
92
+ /** The source reference exactly as the document writes it. */
93
+ src: string;
94
+ key: string;
95
+ /** Canonical known-op `fx` the render applied. */
96
+ fx: string;
97
+ /** Recipe tokens the rendering engine did not understand (not applied). */
98
+ ignoredOps: string[];
99
+ /** Render file path, relative to the media root. */
100
+ file: string;
101
+ mimeType: string;
102
+ /** Source byte size at render time (staleness check), when known. */
103
+ sourceSize: number | null;
104
+ /** Source audio duration in seconds at render time, when known. */
105
+ sourceDuration: number | null;
106
+ /** Engine identifier, e.g. `squisq-media-edit/1`. */
107
+ engine: string;
108
+ /** ISO-8601 creation time. */
109
+ createdAt: string;
110
+ analysis?: MediaRenderAnalysis;
111
+ }
112
+ /** A render the index found in a listing. */
113
+ interface MediaRenderEntry {
114
+ key: string;
115
+ /** Render file path as listed (may carry the media folder prefix). */
116
+ path: string;
117
+ /** Manifest path, when a manifest was listed beside it. */
118
+ manifestPath: string | null;
119
+ size: number;
120
+ }
121
+ /** Filesystem-safe stem from a source reference's basename (`take.webm` → `take`). */
122
+ declare function mediaRenderStem(src: string): string;
123
+ declare function mediaRenderFilePath(src: string, key: string, extension: string): string;
124
+ declare function mediaRenderManifestPath(src: string, key: string): string;
125
+ interface ParsedMediaRenderPath {
126
+ stem: string;
127
+ key: string;
128
+ extension: string;
129
+ isManifest: boolean;
130
+ }
131
+ /**
132
+ * Recognize a render or manifest path, with or without a leading media
133
+ * folder (`notes_files/.mediaEdits/take.1a2b3c4d5e6f.webm`).
134
+ */
135
+ declare function parseMediaRenderPath(path: string): ParsedMediaRenderPath | null;
136
+ declare function isMediaRenderPath(path: string): boolean;
137
+ /**
138
+ * Index a media listing by render key. Entries that are not renders are
139
+ * ignored; a render whose manifest is missing is still indexed (it plays),
140
+ * with `manifestPath: null`.
141
+ */
142
+ declare function buildMediaRenderIndex(entries: ReadonlyArray<{
143
+ name: string;
144
+ size: number;
145
+ }>): Map<string, MediaRenderEntry>;
146
+ /** Validate manifest JSON; null when malformed or from an unknown version. */
147
+ declare function parseMediaRenderManifest(text: string): MediaRenderManifest | null;
148
+ declare function serializeMediaRenderManifest(manifest: MediaRenderManifest): string;
149
+ /**
150
+ * Why a render no longer matches its source, or null when it is current.
151
+ *
152
+ * - `source-changed` — the source file's size differs from render time
153
+ * (a re-recorded take under the same name).
154
+ * - `engine-upgraded` — the render skipped ops the current engine now knows,
155
+ * so re-rendering would apply them.
156
+ */
157
+ declare function mediaRenderStaleness(manifest: MediaRenderManifest, current: {
158
+ sourceSize?: number | null;
159
+ knownOps?: readonly MediaFxOpId[];
160
+ }): 'source-changed' | 'engine-upgraded' | null;
161
+ /** Every media clip in a doc (block-anchored and document-anchored). */
162
+ declare function collectDocMediaClips(doc: Doc): MediaClip[];
163
+ /** The render key a clip needs, or null when its recipe renders nothing. */
164
+ declare function mediaClipRenderKey(clip: MediaClip): string | null;
165
+ /** Render keys referenced by the doc's current recipes. */
166
+ declare function referencedMediaRenderKeys(doc: Doc): Set<string>;
167
+ /** Render key for a raw `src` + `fx` string pair (for callers holding strings). */
168
+ declare function mediaRenderKeyFor(src: string, fx: string | undefined): string | null;
169
+ /**
170
+ * Paths eligible for garbage collection: renders (and their manifests) whose
171
+ * key no current recipe references and whose manifest is older than
172
+ * `olderThanDays`. A render without a manifest has no age, so it is collected
173
+ * only once unreferenced — it cannot be the cache of a pending undo, because
174
+ * every render this engine writes carries a manifest.
175
+ */
176
+ declare function selectMediaRendersForGc(index: ReadonlyMap<string, MediaRenderEntry>, referencedKeys: ReadonlySet<string>, manifests: ReadonlyMap<string, MediaRenderManifest>, now?: Date, olderThanDays?: number): string[];
177
+
178
+ /**
179
+ * Breath detection for spoken-word recordings.
180
+ *
181
+ * A breath is unvoiced, noise-like energy that sits in a pause: quieter than
182
+ * speech, louder than the room, lasting roughly 0.12–1 s, and — unlike an
183
+ * unvoiced consonant (s, f, sh, h) — not wedged tightly between voiced frames.
184
+ * Detection runs on 10 ms frames:
185
+ *
186
+ * - `rmsDb` full-band level.
187
+ * - `voicing` peak normalized autocorrelation in the 70–400 Hz pitch range,
188
+ * on a low-passed, decimated copy (cheap enough for long takes).
189
+ * - `zcr` zero-crossing rate; sibilants cross far more often than breaths.
190
+ *
191
+ * Levels are relative to the take itself (a speech level from voiced frames
192
+ * and a noise floor from the quietest frames), so the detector adapts to mic
193
+ * gain. The result is attenuation regions — the chain turns them into a gain
194
+ * envelope and never deletes audio, so timing is untouched.
195
+ */
196
+ interface BreathFrame {
197
+ /** Frame center, seconds. */
198
+ t: number;
199
+ rmsDb: number;
200
+ /** 0 (noise) … 1 (strongly periodic). */
201
+ voicing: number;
202
+ zcr: number;
203
+ }
204
+ interface BreathRegion {
205
+ start: number;
206
+ end: number;
207
+ }
208
+ interface BreathAnalyzer {
209
+ /** Feed mono samples (any block size). */
210
+ push(mono: Float32Array): void;
211
+ /** Frames analyzed so far. */
212
+ frames(): BreathFrame[];
213
+ }
214
+ declare function createBreathAnalyzer(sampleRate: number): BreathAnalyzer;
215
+ interface BreathDetectOptions {
216
+ /** 0 (only obvious breaths) … 1 (aggressive). Default 0.5. */
217
+ sensitivity?: number;
218
+ /** Shortest breath, seconds. Default 0.12. */
219
+ minDurationSec?: number;
220
+ /** Longest breath, seconds. Default 1.0. */
221
+ maxDurationSec?: number;
222
+ }
223
+ /** Find breath regions in analyzed frames. */
224
+ declare function detectBreaths(frames: readonly BreathFrame[], options?: BreathDetectOptions): BreathRegion[];
225
+ /**
226
+ * Gain at time `t` for attenuating `regions` by `reductionDb` (≤ 0), ramping
227
+ * over `rampSec` INSIDE each region so surrounding speech is never touched.
228
+ */
229
+ declare function breathGainAt(regions: readonly BreathRegion[], reductionDb: number, t: number, rampSec?: number): number;
230
+ /** Multiply a block starting at `startSec` by the breath envelope, in place. */
231
+ declare function applyBreathEnvelope(channels: readonly Float32Array[], sampleRate: number, startSec: number, regions: readonly BreathRegion[], reductionDb: number): void;
232
+
233
+ /**
234
+ * Denoiser seam. Core defines the contract; engines (an RNNoise WASM build,
235
+ * later possibly DeepFilterNet) live with the renderer, which injects a
236
+ * factory. Keeping the model out of core keeps core dependency-free and lets
237
+ * each surface ship the engine as a bundled asset.
238
+ */
239
+ interface Denoiser {
240
+ /** Samples per frame the model consumes (RNNoise: 480 at 48 kHz). */
241
+ readonly frameSize: number;
242
+ /** Sample rate the model runs at. The chain feeds exactly this rate. */
243
+ readonly sampleRate: number;
244
+ /** Samples by which the model's output lags its input (beyond frame buffering). */
245
+ readonly latency: number;
246
+ /** Denoise one frame in place. Samples are floats in ±1. */
247
+ processFrame(frame: Float32Array): void;
248
+ dispose(): void;
249
+ }
250
+ type DenoiserFactory = () => Promise<Denoiser>;
251
+ /**
252
+ * Streaming wrapper for one channel: accepts any block size, returns a block
253
+ * of the same size, delayed by {@link DenoiseStage.latency}. `strength`
254
+ * blends the denoised signal with the equally delayed dry signal (RNNoise has
255
+ * no strength control of its own), so partial settings never comb-filter.
256
+ */
257
+ interface DenoiseStage {
258
+ readonly latency: number;
259
+ process(block: Float32Array): Float32Array;
260
+ dispose(): void;
261
+ }
262
+ declare function createDenoiseStage(denoiser: Denoiser, strength: number): DenoiseStage;
263
+
264
+ /**
265
+ * The media-edit signal chain: high-pass → denoise → de-breath → loudness
266
+ * (+ true-peak limiter), in that fixed order.
267
+ *
268
+ * Two passes over the source, so memory stays bounded for long takes:
269
+ *
270
+ * 1. **Analyze** — run high-pass + denoise, collect breath features and
271
+ * K-weighted loudness per 100 ms step. Breaths are detected over the whole
272
+ * take (levels are relative to it), and the loudness the output WILL have
273
+ * after breath attenuation is predicted by re-weighting each step by the
274
+ * envelope's mean square, so no second analysis pass is needed.
275
+ * 2. **Render** — run high-pass + denoise again (deterministic), apply the
276
+ * breath envelope and the loudness gain, limit, write.
277
+ *
278
+ * Every stage's latency is compensated: output sample n is source sample n,
279
+ * and the output is exactly as long as the input. That is the render
280
+ * contract (renders are sample-aligned with the source), and it is what lets
281
+ * cuts and trims apply identically to picture and processed sound.
282
+ *
283
+ * Pure: the source, the denoiser, and the sink are injected.
284
+ */
285
+
286
+ /** The rate the chain runs at (RNNoise's native rate). Sources are resampled to it. */
287
+ declare const MEDIA_FX_SAMPLE_RATE = 48000;
288
+ /** Engine identifier recorded in render manifests. */
289
+ declare const MEDIA_FX_ENGINE = "squisq-media-edit/1";
290
+ /** True-peak ceiling after the loudness stage, dBTP. */
291
+ declare const MEDIA_FX_TRUE_PEAK_CEILING_DB = -1;
292
+ interface MediaFxSource {
293
+ /** Must equal {@link MEDIA_FX_SAMPLE_RATE}; the engine resamples first. */
294
+ sampleRate: number;
295
+ channels: number;
296
+ /** A fresh stream of the whole source, as per-channel blocks. Called once per pass. */
297
+ open(): AsyncIterable<Float32Array[]>;
298
+ /** Total frames, when known (for progress). */
299
+ frames?: number;
300
+ }
301
+ interface MediaFxAnalysis {
302
+ /** Integrated loudness entering the loudness stage (after breath attenuation), LUFS. */
303
+ integratedLufs: number | null;
304
+ /** Gain the loudness stage applies, dB. */
305
+ loudnessGainDb: number;
306
+ /** Breath regions in take seconds. */
307
+ breaths: BreathRegion[];
308
+ }
309
+ interface MediaFxOptions {
310
+ fx: MediaFxChain;
311
+ source: MediaFxSource;
312
+ /** Required for `denoise`; without it the op is skipped and reported in `ignoredOps`. */
313
+ denoiser?: DenoiserFactory;
314
+ /** Take time (s) of the source's first sample — nonzero for region previews. */
315
+ startSec?: number;
316
+ onProgress?: (fraction: number) => void;
317
+ signal?: AbortSignal;
318
+ }
319
+ interface MediaFxRenderOptions extends MediaFxOptions {
320
+ write: (block: Float32Array[]) => void | Promise<void>;
321
+ /** Whole-take analysis to reuse (region previews); skips the analysis pass. */
322
+ analysis?: MediaFxAnalysis;
323
+ }
324
+ interface MediaFxRenderResult {
325
+ analysis: MediaFxAnalysis;
326
+ /** Frames written (always the source length). */
327
+ frames: number;
328
+ /** Recipe tokens not applied (unknown ops, or denoise with no denoiser). */
329
+ ignoredOps: string[];
330
+ /** Smallest limiter gain (1 = the limiter never engaged). */
331
+ limiterMinGain: number;
332
+ /**
333
+ * Integrated loudness of what was written, when the recipe has a loudness
334
+ * target. Limiting can pull it below the target; callers may re-render with
335
+ * a corrected gain (see {@link correctedLoudnessAnalysis}).
336
+ */
337
+ outputLufs: number | null;
338
+ }
339
+ /** Analysis pass only: breaths and the loudness gain a render would apply. */
340
+ declare function analyzeMediaFx(options: MediaFxOptions): Promise<MediaFxAnalysis>;
341
+ /** Render the chain: analyze (unless given), then process and write the whole source. */
342
+ declare function renderMediaFx(options: MediaFxRenderOptions): Promise<MediaFxRenderResult>;
343
+ /** Largest deviation from the loudness target accepted without a corrective pass, LU. */
344
+ declare const MEDIA_FX_LOUDNESS_TOLERANCE = 0.5;
345
+ /**
346
+ * When a render missed its loudness target by more than the tolerance (the
347
+ * limiter shaved peaks after the gain was chosen), the analysis to re-render
348
+ * with: the same breaths, gain nudged by the miss. Null when no correction is
349
+ * needed or possible.
350
+ */
351
+ declare function correctedLoudnessAnalysis(fx: MediaFxChain, result: MediaFxRenderResult): MediaFxAnalysis | null;
352
+
353
+ /**
354
+ * Streaming sample-rate conversion (windowed sinc, arbitrary ratio).
355
+ *
356
+ * The chain runs at 48 kHz (the denoiser's rate); sources recorded by
357
+ * MediaRecorder already are, but MP3/M4A imports are often 44.1 kHz. The
358
+ * kernel is symmetric, so output sample j is exactly time j / outRate — the
359
+ * conversion adds no delay, which the time-aligned render contract requires.
360
+ * A Blackman-windowed sinc, tabulated and linearly interpolated, low-passed
361
+ * at the lower Nyquist when downsampling.
362
+ */
363
+ interface Resampler {
364
+ readonly inRate: number;
365
+ readonly outRate: number;
366
+ /** Convert a block; returns however many output samples are now determined. */
367
+ process(input: Float32Array): Float32Array;
368
+ /** Emit the remaining output (call once, after the last block). */
369
+ flush(): Float32Array;
370
+ }
371
+ declare function createResampler(inRate: number, outRate: number, halfTaps?: number): Resampler;
372
+
373
+ /**
374
+ * Integrated loudness per ITU-R BS.1770-4 (the measurement EBU R128 uses).
375
+ *
376
+ * Each channel is K-weighted (a +4 dB high shelf, then an RLB high-pass),
377
+ * mean-square energy is taken over 400 ms blocks overlapping by 75 %, and
378
+ * blocks are gated twice: absolutely at −70 LUFS, then relatively at 10 LU
379
+ * below the absolute-gated loudness. Streaming: energy accumulates per 100 ms
380
+ * step, so memory is ten numbers per second regardless of signal length.
381
+ *
382
+ * The K-weighting filters are derived per sample rate and reproduce the
383
+ * standard's 48 kHz coefficients, so any sample rate measures correctly.
384
+ */
385
+ interface LoudnessMeter {
386
+ /** Feed one block of per-channel samples (equal lengths). */
387
+ push(channels: readonly Float32Array[]): void;
388
+ /**
389
+ * Per-100 ms-step mean-square sums across channels (K-weighted), for
390
+ * callers that re-weight steps (e.g. to predict the effect of a gain
391
+ * envelope without re-running the signal). Complete steps only.
392
+ */
393
+ stepPowers(): readonly number[];
394
+ /** Integrated loudness in LUFS so far; −Infinity for silence or too-short input. */
395
+ integrated(): number;
396
+ }
397
+ declare function createLoudnessMeter(sampleRate: number, channelCount: number): LoudnessMeter;
398
+ /**
399
+ * Gated integrated loudness from per-100 ms-step mean-square sums (as
400
+ * {@link LoudnessMeter.stepPowers} produces). Exposed so a caller can rescale
401
+ * steps — e.g. by a gain envelope's mean square per step — and re-measure.
402
+ */
403
+ declare function integratedLoudnessFromSteps(steps: readonly number[]): number;
404
+
405
+ /**
406
+ * Look-ahead true-peak limiter (streaming, multi-channel, linked).
407
+ *
408
+ * Peaks are estimated between samples by 4× polyphase windowed-sinc
409
+ * interpolation, so the ceiling holds for the reconstructed waveform, not
410
+ * just the stored samples. The gain needed at each instant is the sliding
411
+ * minimum of `ceiling / peak` over the next `lookahead` samples, box-smoothed
412
+ * over the same length — every smoothing window that averages into sample
413
+ * `t` also saw `t`'s own requirement, so the smoothed gain never exceeds it.
414
+ * A one-pole release then lets gain recover slowly. The signal is delayed by
415
+ * the look-ahead; {@link Limiter.flush} drains the tail.
416
+ */
417
+ interface LimiterOptions {
418
+ sampleRate: number;
419
+ channels: number;
420
+ /** Ceiling in dBTP. Default −1. */
421
+ ceilingDb?: number;
422
+ /** Look-ahead in ms. Default 5. */
423
+ lookaheadMs?: number;
424
+ /** Release time constant in ms. Default 80. */
425
+ releaseMs?: number;
426
+ }
427
+ interface Limiter {
428
+ /** Delay the limiter adds, in samples. */
429
+ readonly latency: number;
430
+ /** Process a block; returns the (delayed) limited output of the same length. */
431
+ process(channels: readonly Float32Array[]): Float32Array[];
432
+ /** Drain the delayed tail (`latency` samples per channel). */
433
+ flush(): Float32Array[];
434
+ /** Smallest gain applied so far (1 = never limited). */
435
+ minGain(): number;
436
+ }
437
+ declare function createLimiter(options: LimiterOptions): Limiter;
438
+
439
+ /**
440
+ * Pause tightening: find long silences in a take and propose cuts that shorten
441
+ * each to a natural length.
442
+ *
443
+ * Works on the breath analyzer's 10 ms frames. A frame is quiet when it is
444
+ * unvoiced and close to the take's noise floor; anything louder — speech,
445
+ * consonants, and breaths — is activity and is never cut, so a cut never lands
446
+ * in the middle of a sound. Each quiet run longer than `maxPauseSec` keeps
447
+ * `keepSec` of silence, split evenly around the cut, and loses the middle.
448
+ * Cuts are in source seconds, ready for a `cuts=` recipe.
449
+ */
450
+
451
+ interface PauseTightenOptions {
452
+ /** Pauses longer than this are shortened. Default 0.8 s. */
453
+ maxPauseSec?: number;
454
+ /** Silence left in place of a shortened pause. Default 0.4 s. */
455
+ keepSec?: number;
456
+ /** Shorter cuts are not worth a seam. Default 0.25 s. */
457
+ minCutSec?: number;
458
+ }
459
+ declare const PAUSE_TIGHTEN_DEFAULTS: Readonly<{
460
+ maxPauseSec: 0.8;
461
+ keepSec: 0.4;
462
+ minCutSec: 0.25;
463
+ }>;
464
+ /** Cuts that shorten every long pause in the take. */
465
+ declare function detectPauseCuts(frames: readonly BreathFrame[], options?: PauseTightenOptions): MediaCut[];
466
+
467
+ export { type BreathAnalyzer, type BreathDetectOptions, type BreathFrame, type BreathRegion, type DenoiseStage, type Denoiser, type DenoiserFactory, type Limiter, type LimiterOptions, type LoudnessMeter, MEDIA_FX_ENGINE, MEDIA_FX_LOUDNESS_TOLERANCE, MEDIA_FX_SAMPLE_RATE, MEDIA_FX_TRUE_PEAK_CEILING_DB, MEDIA_RENDER_DIR, MEDIA_RENDER_GC_AGE_DAYS, MEDIA_RENDER_MANIFEST_VERSION, MediaCut, type MediaFxAnalysis, MediaFxChain, MediaFxOpId, type MediaFxOptions, type MediaFxRenderOptions, type MediaFxRenderResult, type MediaFxSource, type MediaRenderAnalysis, type MediaRenderEntry, type MediaRenderManifest, type MediaTimeMap, type MediaTimeMapInput, PAUSE_TIGHTEN_DEFAULTS, type ParsedMediaRenderPath, type PauseTightenOptions, type Resampler, analyzeMediaFx, applyBreathEnvelope, breathGainAt, buildMediaRenderIndex, collectDocMediaClips, correctedLoudnessAnalysis, createBreathAnalyzer, createDenoiseStage, createLimiter, createLoudnessMeter, createMediaTimeMap, createResampler, detectBreaths, detectPauseCuts, integratedLoudnessFromSteps, isMediaRenderPath, mediaClipRenderKey, mediaRenderFilePath, mediaRenderKeyFor, mediaRenderManifestPath, mediaRenderStaleness, mediaRenderStem, parseMediaRenderManifest, parseMediaRenderPath, referencedMediaRenderKeys, renderMediaFx, selectMediaRendersForGc, serializeMediaRenderManifest };