auditok 0.1.0

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.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Node entry point: everything from the core, plus file loading
3
+ * (native WAV parser, ffmpeg for everything else), microphone capture
4
+ * through ffmpeg, and WAV saving. Zero npm runtime dependencies.
5
+ */
6
+ import { readFile, writeFile } from "node:fs/promises";
7
+ import { AudioRegion } from "../region.js";
8
+ import { decodeWav, encodeWav, isWav, } from "../wav.js";
9
+ import { decodeFileStream } from "./ffmpeg.js";
10
+ export * from "../index.js";
11
+ export { decodeFileStream, microphone, } from "./ffmpeg.js";
12
+ /**
13
+ * Load an audio file into an {@link AudioRegion}. WAV files are parsed
14
+ * natively; every other format is decoded with ffmpeg (which must be
15
+ * on the PATH). To detect events while a large compressed file is
16
+ * still being decoded, use {@link decodeFileStream} with `split`
17
+ * instead of loading it fully.
18
+ */
19
+ export async function load(path, options = {}) {
20
+ if (options.forceFfmpeg !== true) {
21
+ const bytes = await readFile(path);
22
+ if (isWav(bytes)) {
23
+ const { channelData, sampleRate } = decodeWav(bytes);
24
+ return new AudioRegion(channelData, sampleRate);
25
+ }
26
+ }
27
+ const stream = decodeFileStream(path, options);
28
+ const chunks = [];
29
+ let total = 0;
30
+ for await (const chunk of stream.chunks) {
31
+ chunks.push(chunk);
32
+ total += chunk.length;
33
+ }
34
+ const { channels, sampleRate } = stream;
35
+ const sampleCount = Math.floor(total / channels);
36
+ const channelData = Array.from({ length: channels }, () => new Float32Array(sampleCount));
37
+ let sample = 0;
38
+ let channelCursor = 0;
39
+ for (const chunk of chunks) {
40
+ for (let i = 0; i < chunk.length && sample < sampleCount; i++) {
41
+ channelData[channelCursor][sample] = chunk[i];
42
+ channelCursor += 1;
43
+ if (channelCursor === channels) {
44
+ channelCursor = 0;
45
+ sample += 1;
46
+ }
47
+ }
48
+ }
49
+ return new AudioRegion(channelData, sampleRate);
50
+ }
51
+ /** Save a region (or any `{ channelData, sampleRate }`) as a WAV file. */
52
+ export async function save(path, audio, options) {
53
+ await writeFile(path, encodeWav(audio, options));
54
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * AudioRegion: a chunk of audio with a position in time. What `split`
3
+ * yields and what `trim`/`fixPauses` return.
4
+ */
5
+ import { type WavEncodeOptions } from "./wav.js";
6
+ export declare class AudioRegion {
7
+ /** Planar samples, one Float32Array per channel, values nominally in
8
+ * [-1, 1]. */
9
+ readonly channelData: readonly Float32Array[];
10
+ readonly sampleRate: number;
11
+ /** Start time in seconds relative to the beginning of the input this
12
+ * region was detected in (0 for regions built directly). */
13
+ readonly start: number;
14
+ constructor(channelData: readonly Float32Array[], sampleRate: number, start?: number);
15
+ get channels(): number;
16
+ /** Number of samples per channel. */
17
+ get sampleCount(): number;
18
+ /** Duration in seconds. */
19
+ get duration(): number;
20
+ /** End time in seconds (`start + duration`). */
21
+ get end(): number;
22
+ /** Encode the region as a WAV file (16-bit PCM by default). */
23
+ toWav(options?: WavEncodeOptions): Uint8Array;
24
+ }
25
+ /** A region of digital silence of the given duration. */
26
+ export declare function makeSilence(duration: number, sampleRate?: number, channels?: number): AudioRegion;
27
+ /**
28
+ * Concatenate `regions` into one region, inserting `gap` (e.g. a
29
+ * silence built with {@link makeSilence}) between consecutive regions.
30
+ */
31
+ export declare function concatRegions(regions: readonly AudioRegion[], gap?: AudioRegion): AudioRegion;
package/dist/region.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * AudioRegion: a chunk of audio with a position in time. What `split`
3
+ * yields and what `trim`/`fixPauses` return.
4
+ */
5
+ import { encodeWav } from "./wav.js";
6
+ export class AudioRegion {
7
+ constructor(channelData, sampleRate, start = 0) {
8
+ if (channelData.length === 0) {
9
+ throw new RangeError("'channelData' must contain at least one channel");
10
+ }
11
+ const length = channelData[0].length;
12
+ for (const channel of channelData) {
13
+ if (channel.length !== length) {
14
+ throw new RangeError("all channels must have the same length");
15
+ }
16
+ }
17
+ if (!(sampleRate > 0)) {
18
+ throw new RangeError(`'sampleRate' must be > 0, given: ${sampleRate}`);
19
+ }
20
+ this.channelData = channelData;
21
+ this.sampleRate = sampleRate;
22
+ this.start = start;
23
+ }
24
+ get channels() {
25
+ return this.channelData.length;
26
+ }
27
+ /** Number of samples per channel. */
28
+ get sampleCount() {
29
+ return this.channelData[0].length;
30
+ }
31
+ /** Duration in seconds. */
32
+ get duration() {
33
+ return this.sampleCount / this.sampleRate;
34
+ }
35
+ /** End time in seconds (`start + duration`). */
36
+ get end() {
37
+ return this.start + this.duration;
38
+ }
39
+ /** Encode the region as a WAV file (16-bit PCM by default). */
40
+ toWav(options) {
41
+ return encodeWav(this, options);
42
+ }
43
+ }
44
+ /** A region of digital silence of the given duration. */
45
+ export function makeSilence(duration, sampleRate = 16000, channels = 1) {
46
+ const sampleCount = Math.round(duration * sampleRate);
47
+ const channelData = Array.from({ length: channels }, () => new Float32Array(sampleCount));
48
+ return new AudioRegion(channelData, sampleRate);
49
+ }
50
+ /**
51
+ * Concatenate `regions` into one region, inserting `gap` (e.g. a
52
+ * silence built with {@link makeSilence}) between consecutive regions.
53
+ */
54
+ export function concatRegions(regions, gap) {
55
+ if (regions.length === 0) {
56
+ throw new RangeError("'regions' must contain at least one region");
57
+ }
58
+ const { sampleRate, channels } = regions[0];
59
+ const parts = [];
60
+ let totalSamples = 0;
61
+ regions.forEach((region, i) => {
62
+ if (region.sampleRate !== sampleRate || region.channels !== channels) {
63
+ throw new RangeError("all regions must have the same sample rate and channel count");
64
+ }
65
+ if (i > 0 && gap !== undefined) {
66
+ parts.push(gap.channelData);
67
+ totalSamples += gap.sampleCount;
68
+ }
69
+ parts.push(region.channelData);
70
+ totalSamples += region.sampleCount;
71
+ });
72
+ const channelData = Array.from({ length: channels }, () => new Float32Array(totalSamples));
73
+ let offset = 0;
74
+ for (const part of parts) {
75
+ for (let c = 0; c < channels; c++) {
76
+ channelData[c].set(part[c], offset);
77
+ }
78
+ offset += part[0].length;
79
+ }
80
+ return new AudioRegion(channelData, sampleRate);
81
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Signal-level operations: window energy and automatic threshold
3
+ * estimation.
4
+ *
5
+ * Samples are Float32Array values nominally in [-1, 1] (the Web Audio
6
+ * convention). Energies are expressed in dB relative to 16-bit full
7
+ * scale — samples are scaled by 32768 before the log — so thresholds
8
+ * mean exactly the same thing as in Python auditok, and its
9
+ * documentation transfers 1:1.
10
+ */
11
+ export declare const EPSILON = 1e-10;
12
+ /** Amplitude scale applied before the log so that dB values are relative
13
+ * to int16 full scale, like in Python auditok. */
14
+ export declare const INT16_SCALE = 32768;
15
+ /** Energy assigned to an all-zero (digitally silent) window:
16
+ * 20 * log10(EPSILON) = -200 dB. A sentinel, not a measurement. */
17
+ export declare const SILENCE_ENERGY: number;
18
+ /** Channel used for energy computation on multichannel audio:
19
+ * "any" (default) takes the maximum energy over channels, "mix"
20
+ * averages the channels into one before computing the energy, an
21
+ * integer selects one channel (negative counts from the end). */
22
+ export type ChannelSelection = "any" | "mix" | number;
23
+ /**
24
+ * Compute the energy of `samples` as
25
+ * `20 * log10(sqrt(mean(x^2)))` with `x` on the int16 amplitude scale.
26
+ * An all-zero window yields {@link SILENCE_ENERGY} (-200 dB).
27
+ */
28
+ export declare function calculateEnergy(samples: ArrayLike<number>): number;
29
+ /**
30
+ * Energy of one analysis window given as planar channel data,
31
+ * aggregated over channels according to `useChannel`.
32
+ */
33
+ export declare function windowEnergy(channelData: readonly Float32Array[], useChannel?: ChannelSelection): number;
34
+ /**
35
+ * Energy of each analysis window of `channelData`, one value per whole
36
+ * window of `frameSamples` samples. A final partial window, if any, is
37
+ * ignored. This is what automatic threshold estimation runs on; the
38
+ * energy formula and channel aggregation are exactly those used by the
39
+ * tokenizer's energy validator, so an estimated threshold means the
40
+ * same thing at detection time.
41
+ */
42
+ export declare function computeFrameEnergies(channelData: readonly Float32Array[], frameSamples: number, useChannel?: ChannelSelection): Float64Array;
43
+ export type ThresholdMethod = "otsu" | "percentile";
44
+ export interface ThresholdMethodOptions {
45
+ /** Percentile read as the noise floor (method "percentile" only),
46
+ * default 10. */
47
+ percentile?: number;
48
+ /** dB margin added to the noise floor (method "percentile" only),
49
+ * default 6. */
50
+ margin?: number;
51
+ }
52
+ /**
53
+ * Estimate an energy threshold from analysis-window energies (as
54
+ * returned by {@link computeFrameEnergies}). The returned value is on
55
+ * the same dB scale as `energyThreshold` and can be passed to `split`
56
+ * directly.
57
+ *
58
+ * All-zero windows carry the -200 dB silence sentinel and are excluded
59
+ * from the estimate; if nothing else remains the input is entirely
60
+ * digitally silent and the function returns `Infinity` (every window
61
+ * must fail validation — there is nothing to detect).
62
+ */
63
+ export declare function estimateEnergyThreshold(frameEnergies: ArrayLike<number>, method?: ThresholdMethod, options?: ThresholdMethodOptions): number;
package/dist/signal.js ADDED
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Signal-level operations: window energy and automatic threshold
3
+ * estimation.
4
+ *
5
+ * Samples are Float32Array values nominally in [-1, 1] (the Web Audio
6
+ * convention). Energies are expressed in dB relative to 16-bit full
7
+ * scale — samples are scaled by 32768 before the log — so thresholds
8
+ * mean exactly the same thing as in Python auditok, and its
9
+ * documentation transfers 1:1.
10
+ */
11
+ export const EPSILON = 1e-10;
12
+ /** Amplitude scale applied before the log so that dB values are relative
13
+ * to int16 full scale, like in Python auditok. */
14
+ export const INT16_SCALE = 32768;
15
+ /** Energy assigned to an all-zero (digitally silent) window:
16
+ * 20 * log10(EPSILON) = -200 dB. A sentinel, not a measurement. */
17
+ export const SILENCE_ENERGY = 20 * Math.log10(EPSILON);
18
+ /**
19
+ * Compute the energy of `samples` as
20
+ * `20 * log10(sqrt(mean(x^2)))` with `x` on the int16 amplitude scale.
21
+ * An all-zero window yields {@link SILENCE_ENERGY} (-200 dB).
22
+ */
23
+ export function calculateEnergy(samples) {
24
+ const n = samples.length;
25
+ if (n === 0) {
26
+ return SILENCE_ENERGY;
27
+ }
28
+ let sumSquares = 0;
29
+ for (let i = 0; i < n; i++) {
30
+ const x = samples[i];
31
+ sumSquares += x * x;
32
+ }
33
+ const rms = Math.sqrt(sumSquares / n) * INT16_SCALE;
34
+ return 20 * Math.log10(Math.max(rms, EPSILON));
35
+ }
36
+ function resolveChannelIndex(index, channels) {
37
+ const selected = index < 0 ? index + channels : index;
38
+ if (!Number.isInteger(index) || selected < 0 || selected >= channels) {
39
+ throw new RangeError(`Selected channel must be >= -channels and < channels, given: ${index}`);
40
+ }
41
+ return selected;
42
+ }
43
+ function mixChannels(channelData, start, end) {
44
+ const mixed = new Float32Array(end - start);
45
+ for (const channel of channelData) {
46
+ for (let i = start; i < end; i++) {
47
+ mixed[i - start] += channel[i];
48
+ }
49
+ }
50
+ const channels = channelData.length;
51
+ for (let i = 0; i < mixed.length; i++) {
52
+ mixed[i] /= channels;
53
+ }
54
+ return mixed;
55
+ }
56
+ /**
57
+ * Energy of one analysis window given as planar channel data,
58
+ * aggregated over channels according to `useChannel`.
59
+ */
60
+ export function windowEnergy(channelData, useChannel = "any") {
61
+ if (channelData.length === 1) {
62
+ return calculateEnergy(channelData[0]);
63
+ }
64
+ if (useChannel === "any") {
65
+ let maxEnergy = -Infinity;
66
+ for (const channel of channelData) {
67
+ const energy = calculateEnergy(channel);
68
+ if (energy > maxEnergy) {
69
+ maxEnergy = energy;
70
+ }
71
+ }
72
+ return maxEnergy;
73
+ }
74
+ if (useChannel === "mix") {
75
+ return calculateEnergy(mixChannels(channelData, 0, channelData[0].length));
76
+ }
77
+ const index = resolveChannelIndex(useChannel, channelData.length);
78
+ return calculateEnergy(channelData[index]);
79
+ }
80
+ /**
81
+ * Energy of each analysis window of `channelData`, one value per whole
82
+ * window of `frameSamples` samples. A final partial window, if any, is
83
+ * ignored. This is what automatic threshold estimation runs on; the
84
+ * energy formula and channel aggregation are exactly those used by the
85
+ * tokenizer's energy validator, so an estimated threshold means the
86
+ * same thing at detection time.
87
+ */
88
+ export function computeFrameEnergies(channelData, frameSamples, useChannel = "any") {
89
+ if (!Number.isInteger(frameSamples) || frameSamples <= 0) {
90
+ throw new RangeError(`'frameSamples' must be a positive integer, given: ${frameSamples}`);
91
+ }
92
+ const totalSamples = channelData[0]?.length ?? 0;
93
+ const nFrames = Math.floor(totalSamples / frameSamples);
94
+ const energies = new Float64Array(nFrames);
95
+ for (let i = 0; i < nFrames; i++) {
96
+ const start = i * frameSamples;
97
+ const end = start + frameSamples;
98
+ const frame = channelData.map((channel) => channel.subarray(start, end));
99
+ energies[i] = windowEnergy(frame, useChannel);
100
+ }
101
+ return energies;
102
+ }
103
+ /** Equal-width histogram matching numpy.histogram: `bins` bins over
104
+ * [min, max], the last bin including its right edge. */
105
+ function histogram(values, bins, min, max) {
106
+ const hist = new Float64Array(bins);
107
+ const edges = new Float64Array(bins + 1);
108
+ for (let i = 0; i <= bins; i++) {
109
+ edges[i] = min + ((max - min) * i) / bins;
110
+ }
111
+ const scale = bins / (max - min);
112
+ for (let i = 0; i < values.length; i++) {
113
+ let bin = Math.floor((values[i] - min) * scale);
114
+ if (bin >= bins) {
115
+ bin = bins - 1;
116
+ }
117
+ else if (bin < 0) {
118
+ bin = 0;
119
+ }
120
+ hist[bin] += 1;
121
+ }
122
+ return { hist, edges };
123
+ }
124
+ /** Otsu's method: split the energy histogram in two classes maximizing
125
+ * the between-class variance. Parameter-free; assumes the energy
126
+ * distribution is roughly bimodal (background vs. activity). */
127
+ function estimateThresholdOtsu(energies, bins = 128) {
128
+ let min = Infinity;
129
+ let max = -Infinity;
130
+ for (const e of energies) {
131
+ if (e < min)
132
+ min = e;
133
+ if (e > max)
134
+ max = e;
135
+ }
136
+ const { hist, edges } = histogram(energies, bins, min, max);
137
+ const centers = new Float64Array(bins);
138
+ for (let i = 0; i < bins; i++) {
139
+ centers[i] = (edges[i] + edges[i + 1]) / 2;
140
+ }
141
+ let total = 0;
142
+ let totalMass = 0;
143
+ for (let i = 0; i < bins; i++) {
144
+ total += hist[i];
145
+ totalMass += hist[i] * centers[i];
146
+ }
147
+ // between-class variance at each split point (split after bin i)
148
+ const betweenVar = new Float64Array(bins - 1);
149
+ let weight0 = 0;
150
+ let cumMass = 0;
151
+ for (let i = 0; i < bins - 1; i++) {
152
+ weight0 += hist[i];
153
+ cumMass += hist[i] * centers[i];
154
+ const weight1 = total - weight0;
155
+ if (weight0 === 0 || weight1 === 0) {
156
+ betweenVar[i] = -1; // matches numpy's nan_to_num(nan=-1)
157
+ continue;
158
+ }
159
+ const mu0 = cumMass / weight0;
160
+ const mu1 = (totalMass - cumMass) / weight1;
161
+ betweenVar[i] = weight0 * weight1 * (mu0 - mu1) ** 2;
162
+ }
163
+ let best = -Infinity;
164
+ for (let i = 0; i < betweenVar.length; i++) {
165
+ if (betweenVar[i] > best)
166
+ best = betweenVar[i];
167
+ }
168
+ // empty bins between the two modes make the between-class variance
169
+ // exactly flat over the gap; take the middle of the plateau (max
170
+ // margin) rather than the leftmost point
171
+ const candidates = [];
172
+ for (let i = 0; i < betweenVar.length; i++) {
173
+ if (betweenVar[i] === best)
174
+ candidates.push(i);
175
+ }
176
+ const split = candidates[(candidates.length - 1) >> 1];
177
+ return edges[split + 1];
178
+ }
179
+ /** Linear-interpolation percentile, matching numpy.percentile. */
180
+ function percentileOf(sorted, percentile) {
181
+ const rank = ((sorted.length - 1) * percentile) / 100;
182
+ const lo = Math.floor(rank);
183
+ const frac = rank - lo;
184
+ if (lo + 1 >= sorted.length) {
185
+ return sorted[sorted.length - 1];
186
+ }
187
+ return sorted[lo] + frac * (sorted[lo + 1] - sorted[lo]);
188
+ }
189
+ /** Noise floor (low percentile of window energies) plus a margin. */
190
+ function estimateThresholdPercentile(energies, percentile, margin) {
191
+ const sorted = [...energies].sort((a, b) => a - b);
192
+ return percentileOf(sorted, percentile) + margin;
193
+ }
194
+ /**
195
+ * Estimate an energy threshold from analysis-window energies (as
196
+ * returned by {@link computeFrameEnergies}). The returned value is on
197
+ * the same dB scale as `energyThreshold` and can be passed to `split`
198
+ * directly.
199
+ *
200
+ * All-zero windows carry the -200 dB silence sentinel and are excluded
201
+ * from the estimate; if nothing else remains the input is entirely
202
+ * digitally silent and the function returns `Infinity` (every window
203
+ * must fail validation — there is nothing to detect).
204
+ */
205
+ export function estimateEnergyThreshold(frameEnergies, method = "otsu", options = {}) {
206
+ if (frameEnergies.length === 0) {
207
+ throw new RangeError("Cannot estimate an energy threshold from an empty energy array " +
208
+ "(input audio shorter than one analysis window?)");
209
+ }
210
+ const energies = [];
211
+ for (let i = 0; i < frameEnergies.length; i++) {
212
+ if (frameEnergies[i] > SILENCE_ENERGY) {
213
+ energies.push(frameEnergies[i]);
214
+ }
215
+ }
216
+ if (energies.length === 0) {
217
+ // the input is entirely digitally silent: there is no energy
218
+ // distribution to estimate from and nothing to detect
219
+ return Infinity;
220
+ }
221
+ let min = Infinity;
222
+ let max = -Infinity;
223
+ for (const e of energies) {
224
+ if (e < min)
225
+ min = e;
226
+ if (e > max)
227
+ max = e;
228
+ }
229
+ if (min === max) {
230
+ return min;
231
+ }
232
+ if (method === "otsu") {
233
+ return estimateThresholdOtsu(energies);
234
+ }
235
+ if (method === "percentile") {
236
+ return estimateThresholdPercentile(energies, options.percentile ?? 10, options.margin ?? 6);
237
+ }
238
+ throw new RangeError(`Unknown threshold estimation method '${method}', expected ` +
239
+ `"otsu" or "percentile"`);
240
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * High-level API: split audio into events, trim leading/trailing
3
+ * silence, normalize pauses. Environment-free — inputs are samples
4
+ * (in-memory or streamed); the io layers (Node, browser) only provide
5
+ * ways to obtain them.
6
+ */
7
+ import { AudioRegion } from "./region.js";
8
+ import { type ChannelSelection } from "./signal.js";
9
+ /** One analysis window as planar per-channel samples. This is what a
10
+ * custom validator function receives. */
11
+ export type AudioFrame = readonly Float32Array[];
12
+ /** Minimal structural type for a Web Audio `AudioBuffer`. */
13
+ export interface AudioBufferLike {
14
+ numberOfChannels: number;
15
+ sampleRate: number;
16
+ getChannelData(channel: number): Float32Array;
17
+ }
18
+ /**
19
+ * Audio input accepted by {@link split}, {@link trim} and
20
+ * {@link fixPauses}:
21
+ *
22
+ * - `Float32Array`: mono samples (requires `sampleRate` in options);
23
+ * - `Float32Array[]`: planar channels of equal length (requires
24
+ * `sampleRate`);
25
+ * - {@link AudioRegion} or any `{ channelData, sampleRate }` object;
26
+ * - a Web Audio `AudioBuffer`;
27
+ * - a sync or async iterable of `Float32Array` chunks of *interleaved*
28
+ * samples (requires `sampleRate`; `channels` defaults to 1). This is
29
+ * the streaming path: events are yielded while the input is still
30
+ * being read.
31
+ */
32
+ export type AudioInput = Float32Array | Float32Array[] | AudioRegion | {
33
+ channelData: readonly Float32Array[];
34
+ sampleRate: number;
35
+ } | AudioBufferLike | Iterable<Float32Array> | AsyncIterable<Float32Array>;
36
+ export interface SplitOptions {
37
+ /** Sample rate in Hz. Required when the input does not carry its
38
+ * own (bare samples or chunk streams). */
39
+ sampleRate?: number;
40
+ /** Number of interleaved channels for chunk-stream input, default 1.
41
+ * Ignored for in-memory input, which is planar. */
42
+ channels?: number;
43
+ /** Minimum duration in seconds of a detected event, default 0.2. */
44
+ minDur?: number;
45
+ /** Maximum duration in seconds of an event, default 5; longer events
46
+ * are truncated. `null` or `Infinity` disables the limit. */
47
+ maxDur?: number | null;
48
+ /** Maximum duration of continuous silence allowed *within* an event,
49
+ * default 0.3. Controls when an event ends. */
50
+ maxSilence?: number;
51
+ /** Silence in seconds to retain before each event (natural attack),
52
+ * default 0. */
53
+ maxLeadingSilence?: number;
54
+ /** Trailing silence in seconds to keep at the end of each event.
55
+ * `null` (default) keeps all trailing silence up to `maxSilence`;
56
+ * `0` drops it; a value larger than `maxSilence` keeps collecting
57
+ * past the event boundary (natural fadeout). */
58
+ maxTrailingSilence?: number | null;
59
+ /** Reject events shorter than `minDur` even when contiguous with a
60
+ * truncated event, default false. */
61
+ strictMinDur?: boolean;
62
+ /** Duration in seconds of the analysis window, default 0.05. */
63
+ analysisWindow?: number;
64
+ /** Energy threshold in dB for detection, default 50. Used when no
65
+ * `validator` is given. Same scale as Python auditok: dB relative to
66
+ * int16 full scale. */
67
+ energyThreshold?: number;
68
+ /**
69
+ * Frame validation strategy; if set, it is the whole story and
70
+ * `energyThreshold` is overlooked. A string selects automatic
71
+ * threshold estimation: `"otsu"`, `"percentile"` (alias of `"p10"`)
72
+ * or `"pXX"` (noise floor read at the XXth percentile of window
73
+ * energies, plus a 6 dB margin). For in-memory input the threshold
74
+ * is estimated from the whole input before detection starts; for
75
+ * chunk-stream input it is calibrated on the first `calibrationDur`
76
+ * seconds (clamped to `minEnergyThreshold`), and the calibration
77
+ * audio is replayed so no data is lost. A function is called with
78
+ * each analysis window (planar per-channel samples) and returns
79
+ * whether the window is valid.
80
+ */
81
+ validator?: string | ((frame: AudioFrame) => boolean);
82
+ /** Duration in seconds of chunk-stream audio used to calibrate the
83
+ * threshold when `validator` is an estimation string, default 3.
84
+ * Ignored for in-memory input, which is estimated as a whole. */
85
+ calibrationDur?: number;
86
+ /** Lower bound in dB for a threshold calibrated on a chunk stream,
87
+ * default 40. Guards against a too-quiet calibration window; also
88
+ * used as the threshold when the calibration audio is digitally
89
+ * silent (e.g., a muted microphone), so detection keeps running. */
90
+ minEnergyThreshold?: number;
91
+ /** Channel used for energy computation on multichannel audio:
92
+ * `"any"` (default, maximum over channels), `"mix"` or a channel
93
+ * index. */
94
+ useChannel?: ChannelSelection;
95
+ }
96
+ /**
97
+ * Split audio into events. Returns an async generator of
98
+ * {@link AudioRegion} objects, yielded as soon as each event's end is
99
+ * decided — for chunk-stream input, while the input is still being
100
+ * read.
101
+ */
102
+ export declare function split(input: AudioInput, options?: SplitOptions): AsyncGenerator<AudioRegion, void, void>;
103
+ /** Like {@link split}, but collects all events into an array. */
104
+ export declare function splitAll(input: AudioInput, options?: SplitOptions): Promise<AudioRegion[]>;
105
+ type TrimFixPausesOptions = Omit<SplitOptions, "maxDur" | "strictMinDur">;
106
+ /**
107
+ * Detect audio activity in `input` and return the audio between the
108
+ * start of the first detection and the end of the last, removing
109
+ * leading and trailing silence. Chunk-stream input is read once and
110
+ * recorded as it is consumed. Returns an empty region (zero duration)
111
+ * if no activity is detected.
112
+ */
113
+ export declare function trim(input: AudioInput, options?: TrimFixPausesOptions): Promise<AudioRegion>;
114
+ /**
115
+ * Normalize pauses: detect events (never truncated) and join them
116
+ * with exactly `silenceDuration` seconds of silence between them.
117
+ * Events shorter than `minDur` are discarded. Returns an empty region
118
+ * (zero duration) if no events are detected.
119
+ */
120
+ export declare function fixPauses(input: AudioInput, silenceDuration: number, options?: TrimFixPausesOptions): Promise<AudioRegion>;
121
+ export {};