@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.
- package/dist/{Doc-BkxT7G80.d.ts → Doc-DmElSKmg.d.ts} +219 -1
- package/dist/{ImageEditDoc-C8ZB54Vv.d.ts → ImageEditDoc-Dm_BETxC.d.ts} +1 -1
- package/dist/{chunk-EF353KGH.js → chunk-2PGC4JDT.js} +1 -1
- package/dist/{chunk-YGGYYBJS.js → chunk-B5STNW6C.js} +2 -2
- package/dist/{chunk-IYZTF4VO.js → chunk-CCRBHFNO.js} +3 -3
- package/dist/{chunk-VU4T74LI.js → chunk-DTM4FG7S.js} +1 -1
- package/dist/chunk-JSGIWW35.js +70 -0
- package/dist/chunk-L7QZORLB.js +230 -0
- package/dist/chunk-NEWNNLWN.js +64 -0
- package/dist/{chunk-6ECMNUIP.js → chunk-OU5FYBOB.js} +170 -4
- package/dist/{chunk-Y4D6TDZT.js → chunk-PCCQLD3G.js} +2 -0
- package/dist/{chunk-SKKBRLLM.js → chunk-QDA7NXDO.js} +7 -42
- package/dist/{chunk-3YNFMFNK.js → chunk-QOVINAB2.js} +18 -9
- package/dist/{chunk-SOIV7KDO.js → chunk-VRTXPGP7.js} +256 -26
- package/dist/{chunk-XJXBI3N5.js → chunk-XZVBR5BP.js} +1 -1
- package/dist/{chunk-GAZKTT4R.js → chunk-YNO6JV36.js} +96 -33
- package/dist/doc/index.d.ts +10 -6
- package/dist/doc/index.js +23 -21
- package/dist/fence/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/imageEdit/index.d.ts +47 -4
- package/dist/imageEdit/index.js +5 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +52 -41
- package/dist/jsonForm/index.d.ts +1 -1
- package/dist/jsonForm/index.js +6 -4
- package/dist/markdown/index.js +3 -3
- package/dist/{materializePageSection-Dp2aHFbc.d.ts → materializePageSection-BfttC9Zj.d.ts} +6 -3
- package/dist/mediaEdit/index.d.ts +467 -0
- package/dist/mediaEdit/index.js +990 -0
- package/dist/narration/index.d.ts +20 -12
- package/dist/narration/index.js +7 -5
- package/dist/schemas/index.d.ts +4 -4
- package/dist/schemas/index.js +10 -4
- package/dist/{themeLibrary-BbSjnGmd.d.ts → themeLibrary-ChnTImby.d.ts} +1 -1
- package/dist/timing/index.d.ts +1 -1
- package/dist/transform/index.d.ts +2 -2
- package/dist/transform/index.js +7 -6
- package/package.json +6 -1
- 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 };
|