@happyvertical/speech 0.102.4 → 0.102.5

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.
@@ -48,6 +48,255 @@ function resampleMono(samples, fromRate, toRate) {
48
48
  return out;
49
49
  }
50
50
  //#endregion
51
+ //#region src/shared/vad.ts
52
+ var VAD_DEFAULTS = {
53
+ silenceMs: 800,
54
+ minSpeechMs: 150,
55
+ preRollMs: 300,
56
+ maxUtteranceMs: 3e4,
57
+ sensitivity: .5,
58
+ calibrateMs: 300
59
+ };
60
+ /** Length of one analysed frame. */
61
+ var VAD_FRAME_MS = 20;
62
+ /** The quietest noise floor assumed (digital silence must not make everything "speech"). */
63
+ var MIN_FLOOR = .0015;
64
+ /** Floor assumed before the room has been heard (a conservative, quiet-room value). */
65
+ var DEFAULT_FLOOR = .003;
66
+ /**
67
+ * Highest floor calibration may settle on. If the opening frames are louder
68
+ * than this they are taken to be speech (or a very noisy room) and the floor
69
+ * stays conservative instead of climbing to speech level.
70
+ */
71
+ var CALIBRATION_FLOOR_CAP = .006;
72
+ /** Calibration uses this low percentile of the opening energies, not the mean. */
73
+ var CALIBRATION_PERCENTILE = .2;
74
+ /** Highest floor slow tracking may reach between utterances. */
75
+ var MAX_FLOOR = .02;
76
+ /** Time constant of the slow noise-floor tracking, in ms. */
77
+ var FLOOR_TRACK_MS = 2e3;
78
+ /** Silence kept at the end of an utterance when it is trimmed, in ms. */
79
+ var TRAILING_KEEP_MS = 250;
80
+ function clamp(value, min, max) {
81
+ return Math.min(max, Math.max(min, value));
82
+ }
83
+ /** Resolve options against the defaults, clamping silly values. */
84
+ function resolveVadOptions(options = {}) {
85
+ const pick = (value, fallback, min) => typeof value === "number" && Number.isFinite(value) ? Math.max(min, value) : fallback;
86
+ return {
87
+ silenceMs: pick(options.silenceMs, VAD_DEFAULTS.silenceMs, 20),
88
+ minSpeechMs: pick(options.minSpeechMs, VAD_DEFAULTS.minSpeechMs, 0),
89
+ preRollMs: pick(options.preRollMs, VAD_DEFAULTS.preRollMs, 0),
90
+ maxUtteranceMs: pick(options.maxUtteranceMs, VAD_DEFAULTS.maxUtteranceMs, 1e3),
91
+ sensitivity: clamp(typeof options.sensitivity === "number" && Number.isFinite(options.sensitivity) ? options.sensitivity : VAD_DEFAULTS.sensitivity, 0, 1),
92
+ calibrateMs: pick(options.calibrateMs, VAD_DEFAULTS.calibrateMs, 0)
93
+ };
94
+ }
95
+ /** Root-mean-square loudness of a block of samples (-1 to 1). */
96
+ function frameEnergy(samples) {
97
+ const n = samples.length;
98
+ if (n === 0) return 0;
99
+ let sum = 0;
100
+ for (let i = 0; i < n; i++) {
101
+ const s = samples[i];
102
+ sum += s * s;
103
+ }
104
+ return Math.sqrt(sum / n);
105
+ }
106
+ function createVadSegmenter(options = {}) {
107
+ const o = resolveVadOptions(options);
108
+ const ratio = 6 - 4 * o.sensitivity;
109
+ const absMin = .02 - .015 * o.sensitivity;
110
+ let floor = DEFAULT_FLOOR;
111
+ let calibratedMs = 0;
112
+ const calibrationEnergies = [];
113
+ let speaking = false;
114
+ let candidateMs = 0;
115
+ let silenceMs = 0;
116
+ let utteranceMs = 0;
117
+ const onThreshold = () => Math.max(floor * ratio, absMin);
118
+ const offThreshold = () => Math.max(onThreshold() * .6, floor * 1.5);
119
+ const reset = () => {
120
+ speaking = false;
121
+ candidateMs = 0;
122
+ silenceMs = 0;
123
+ utteranceMs = 0;
124
+ };
125
+ return {
126
+ get speaking() {
127
+ return speaking;
128
+ },
129
+ get noiseFloor() {
130
+ return floor;
131
+ },
132
+ get threshold() {
133
+ return onThreshold();
134
+ },
135
+ push(energy, frameMs = 20) {
136
+ const events = [];
137
+ const e = Number.isFinite(energy) ? Math.max(0, energy) : 0;
138
+ const level = clamp(e / (onThreshold() * 3), 0, 1);
139
+ const calibrating = calibratedMs < o.calibrateMs;
140
+ if (calibrating) {
141
+ calibratedMs += frameMs;
142
+ if (!speaking) {
143
+ calibrationEnergies.push(e);
144
+ const sorted = [...calibrationEnergies].sort((a, b) => a - b);
145
+ floor = clamp(sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * CALIBRATION_PERCENTILE))] ?? DEFAULT_FLOOR, MIN_FLOOR, CALIBRATION_FLOOR_CAP);
146
+ }
147
+ }
148
+ if (!speaking) {
149
+ if (e > onThreshold()) {
150
+ candidateMs += frameMs;
151
+ if (candidateMs >= o.minSpeechMs) {
152
+ speaking = true;
153
+ silenceMs = 0;
154
+ utteranceMs = candidateMs;
155
+ events.push({
156
+ type: "start",
157
+ leadMs: o.preRollMs + candidateMs
158
+ });
159
+ candidateMs = 0;
160
+ }
161
+ } else if (!calibrating) {
162
+ candidateMs = 0;
163
+ const alpha = frameMs / FLOOR_TRACK_MS * (e < floor ? 4 : 1);
164
+ floor = clamp(floor + (e - floor) * Math.min(1, alpha), MIN_FLOOR, MAX_FLOOR);
165
+ } else candidateMs = 0;
166
+ return {
167
+ events,
168
+ level,
169
+ speaking
170
+ };
171
+ }
172
+ utteranceMs += frameMs;
173
+ if (e > offThreshold()) silenceMs = 0;
174
+ else silenceMs += frameMs;
175
+ if (silenceMs >= o.silenceMs) {
176
+ events.push({
177
+ type: "end",
178
+ reason: "silence",
179
+ trailingSilenceMs: silenceMs
180
+ });
181
+ reset();
182
+ } else if (utteranceMs >= o.maxUtteranceMs) {
183
+ events.push({ type: "split" });
184
+ utteranceMs = 0;
185
+ silenceMs = 0;
186
+ }
187
+ return {
188
+ events,
189
+ level,
190
+ speaking
191
+ };
192
+ },
193
+ flush() {
194
+ const events = [];
195
+ if (speaking) events.push({
196
+ type: "end",
197
+ reason: "flush",
198
+ trailingSilenceMs: silenceMs
199
+ });
200
+ reset();
201
+ return events;
202
+ }
203
+ };
204
+ }
205
+ function createUtteranceCollector(options) {
206
+ const o = resolveVadOptions(options);
207
+ const segmenter = createVadSegmenter(o);
208
+ const frameSize = Math.max(1, Math.round(options.sampleRate * 20 / 1e3));
209
+ const frameMs = frameSize / options.sampleRate * 1e3;
210
+ const ringFrames = Math.ceil((o.preRollMs + o.minSpeechMs) / frameMs) + 1;
211
+ const ring = [];
212
+ let carry = /* @__PURE__ */ new Float32Array(0);
213
+ let utterance = null;
214
+ let wasSpeaking = false;
215
+ const emit = (frames, reason, trailMs) => {
216
+ const dropFrames = Math.max(0, Math.floor((trailMs - TRAILING_KEEP_MS) / frameMs));
217
+ const kept = frames.slice(0, Math.max(1, frames.length - dropFrames));
218
+ let total = 0;
219
+ for (const f of kept) total += f.length;
220
+ const pcm = new Float32Array(total);
221
+ let at = 0;
222
+ for (const f of kept) {
223
+ pcm.set(f, at);
224
+ at += f.length;
225
+ }
226
+ options.onUtterance({
227
+ pcm,
228
+ sampleRate: options.sampleRate,
229
+ durationMs: total / options.sampleRate * 1e3,
230
+ reason
231
+ });
232
+ };
233
+ const setSpeaking = (speaking) => {
234
+ if (speaking === wasSpeaking) return;
235
+ wasSpeaking = speaking;
236
+ options.onSpeaking?.(speaking);
237
+ };
238
+ const handleFrame = (frame) => {
239
+ const step = segmenter.push(frameEnergy(frame), frameMs);
240
+ options.onLevel?.(step.level);
241
+ ring.push(frame);
242
+ if (ring.length > ringFrames) ring.shift();
243
+ if (utterance) utterance.push(frame);
244
+ for (const event of step.events) if (event.type === "start") {
245
+ utterance = [...ring];
246
+ const wantLead = Math.ceil(o.preRollMs / frameMs);
247
+ const haveLead = Math.max(0, ring.length - Math.ceil(o.minSpeechMs / frameMs));
248
+ for (let i = haveLead; i < wantLead; i++) utterance.unshift(new Float32Array(frameSize));
249
+ } else if (event.type === "end") {
250
+ const frames = utterance ?? [];
251
+ utterance = null;
252
+ ring.length = 0;
253
+ if (frames.length > 0) emit(frames, event.reason, event.trailingSilenceMs);
254
+ } else if (event.type === "split") {
255
+ const frames = utterance ?? [];
256
+ utterance = [];
257
+ if (frames.length > 0) emit(frames, "max", 0);
258
+ }
259
+ setSpeaking(segmenter.speaking);
260
+ };
261
+ return {
262
+ get speaking() {
263
+ return segmenter.speaking;
264
+ },
265
+ segmenter,
266
+ push(samples) {
267
+ let data = samples;
268
+ if (carry.length > 0) {
269
+ data = new Float32Array(carry.length + samples.length);
270
+ data.set(carry, 0);
271
+ data.set(samples, carry.length);
272
+ }
273
+ let at = 0;
274
+ while (at + frameSize <= data.length) {
275
+ handleFrame(data.slice(at, at + frameSize));
276
+ at += frameSize;
277
+ }
278
+ carry = data.slice(at);
279
+ },
280
+ flush() {
281
+ const events = segmenter.flush();
282
+ for (const event of events) if (event.type === "end" && utterance && utterance.length > 0) emit(utterance, "flush", event.trailingSilenceMs);
283
+ utterance = null;
284
+ ring.length = 0;
285
+ carry = /* @__PURE__ */ new Float32Array(0);
286
+ setSpeaking(false);
287
+ options.onLevel?.(0);
288
+ },
289
+ discard() {
290
+ segmenter.flush();
291
+ utterance = null;
292
+ ring.length = 0;
293
+ carry = /* @__PURE__ */ new Float32Array(0);
294
+ setSpeaking(false);
295
+ options.onLevel?.(0);
296
+ }
297
+ };
298
+ }
299
+ //#endregion
51
300
  //#region src/shared/wav.ts
52
301
  /**
53
302
  * Pure, dependency-free 16-bit PCM WAV helpers. No Node APIs and no DOM: they
@@ -227,6 +476,6 @@ function parseWavPcm16(bytes, expect = {}) {
227
476
  };
228
477
  }
229
478
  //#endregion
230
- export { MAX_RESAMPLE_OUTPUT_SAMPLES as a, pcm16ToFloat32 as i, float32ToPcm16 as n, resampleMono as o, parseWavPcm16 as r, encodeWavPcm16 as t };
479
+ export { VAD_DEFAULTS as a, createVadSegmenter as c, MAX_RESAMPLE_OUTPUT_SAMPLES as d, resampleMono as f, pcm16ToFloat32 as i, frameEnergy as l, float32ToPcm16 as n, VAD_FRAME_MS as o, parseWavPcm16 as r, createUtteranceCollector as s, encodeWavPcm16 as t, resolveVadOptions as u };
231
480
 
232
- //# sourceMappingURL=wav-B0Djpj2r.js.map
481
+ //# sourceMappingURL=wav-vzrhhZbu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav-vzrhhZbu.js","names":[],"sources":["../../src/shared/resample.ts","../../src/shared/vad.ts","../../src/shared/wav.ts"],"sourcesContent":["/**\n * Mono resampler: windowed-sinc (Hann, 16 zero crossings per side)\n * interpolation. When downsampling, the kernel's cutoff is scaled to 95% of\n * the target Nyquist frequency so content above it is attenuated before it can\n * alias; when upsampling it is a plain band-limited interpolator. Edges treat\n * missing samples as absent and renormalise the kernel, so DC gain stays 1.\n * Quality is suited to speech recognition (passband flat to roughly 90% of\n * Nyquist, stopband well below -40 dB), not mastering. Cost is\n * O(output length x taps), taps ~ 32 x max(1, fromRate / toRate).\n */\n\nimport { WavFormatError } from './errors.js';\n\n/** Largest output a single call may produce (samples). */\nexport const MAX_RESAMPLE_OUTPUT_SAMPLES = 2 ** 28;\n\nconst ZERO_CROSSINGS = 16;\nconst DOWNSAMPLE_CUTOFF = 0.95;\n\nfunction sinc(x: number): number {\n if (x === 0) return 1;\n const px = Math.PI * x;\n return Math.sin(px) / px;\n}\n\n/** Resamples mono float samples from `fromRate` to `toRate` (Hz). */\nexport function resampleMono(\n samples: Float32Array,\n fromRate: number,\n toRate: number,\n): Float32Array {\n for (const [name, rate] of [\n ['fromRate', fromRate],\n ['toRate', toRate],\n ] as const) {\n if (!Number.isFinite(rate) || rate <= 0) {\n throw new WavFormatError(\n 'invalid_argument',\n `${name} must be a positive finite number (got ${String(rate)})`,\n );\n }\n }\n const length = Math.floor((samples.length * toRate) / fromRate);\n if (length > MAX_RESAMPLE_OUTPUT_SAMPLES) {\n throw new WavFormatError(\n 'invalid_argument',\n `resampled output would be ${length} samples; the limit is ${MAX_RESAMPLE_OUTPUT_SAMPLES}`,\n );\n }\n if (fromRate === toRate) return samples.slice();\n const out = new Float32Array(length);\n const step = fromRate / toRate;\n // Cutoff relative to the input Nyquist; 1 means no low-pass.\n const cutoff = step > 1 ? DOWNSAMPLE_CUTOFF / step : 1;\n const halfWidth = ZERO_CROSSINGS / cutoff;\n const last = samples.length - 1;\n\n for (let index = 0; index < length; index += 1) {\n const center = index * step;\n const first = Math.max(0, Math.ceil(center - halfWidth));\n const end = Math.min(last, Math.floor(center + halfWidth));\n let sum = 0;\n let weights = 0;\n for (let tap = first; tap <= end; tap += 1) {\n const distance = tap - center;\n const window = 0.5 + 0.5 * Math.cos((Math.PI * distance) / halfWidth);\n const weight = cutoff * sinc(cutoff * distance) * window;\n sum += weight * samples[tap];\n weights += weight;\n }\n out[index] = weights !== 0 ? sum / weights : 0;\n }\n return out;\n}\n","/**\n * Voice activity detection for hands-free capture.\n *\n * Energy based, no model and no download: the loudness (RMS) of short audio\n * frames is compared with a noise floor learned from the room. Speech onset\n * starts an utterance and a pause ends it. No Node APIs, no DOM; safe in\n * browsers, workers and Node.\n *\n * Two pure pieces:\n * - `createVadSegmenter`: fed one frame energy at a time, answers with\n * `start` / `end` / `split` events and a live level;\n * - `createUtteranceCollector`: fed raw mono PCM, keeps a pre-roll so the\n * first syllable is not clipped and hands over each finished utterance.\n *\n * `createVadCapture` (`@happyvertical/speech/browser`) drives the collector\n * from a microphone.\n */\n\n/** Tuning for the detector. Every field is optional. */\nexport interface VadOptions {\n /** Silence that ends an utterance, in ms. Default 800. */\n silenceMs?: number;\n /** Speech shorter than this is a click or a cough, not an utterance. Default 150. */\n minSpeechMs?: number;\n /** Audio kept from before the onset so the first syllable is not clipped. Default 300. */\n preRollMs?: number;\n /** An utterance this long is cut and the next one starts at once. Default 30000. */\n maxUtteranceMs?: number;\n /**\n * 0 (needs loud, close speech) to 1 (picks up quiet speech, and more\n * noise). Default 0.5.\n */\n sensitivity?: number;\n /**\n * How long the room is listened to before the floor is settled, in ms.\n * Default 300. Speech is still detected during this window (with a\n * conservative floor), and calibration only uses a low percentile of the\n * energies, so speaking from the first frame does not poison the floor.\n */\n calibrateMs?: number;\n}\n\nexport const VAD_DEFAULTS = {\n silenceMs: 800,\n minSpeechMs: 150,\n preRollMs: 300,\n maxUtteranceMs: 30_000,\n sensitivity: 0.5,\n calibrateMs: 300,\n} as const satisfies Required<VadOptions>;\n\n/** Length of one analysed frame. */\nexport const VAD_FRAME_MS = 20;\n\n/** The quietest noise floor assumed (digital silence must not make everything \"speech\"). */\nconst MIN_FLOOR = 0.0015;\n/** Floor assumed before the room has been heard (a conservative, quiet-room value). */\nconst DEFAULT_FLOOR = 0.003;\n/**\n * Highest floor calibration may settle on. If the opening frames are louder\n * than this they are taken to be speech (or a very noisy room) and the floor\n * stays conservative instead of climbing to speech level.\n */\nconst CALIBRATION_FLOOR_CAP = 0.006;\n/** Calibration uses this low percentile of the opening energies, not the mean. */\nconst CALIBRATION_PERCENTILE = 0.2;\n/** Highest floor slow tracking may reach between utterances. */\nconst MAX_FLOOR = 0.02;\n/** Time constant of the slow noise-floor tracking, in ms. */\nconst FLOOR_TRACK_MS = 2000;\n/** Silence kept at the end of an utterance when it is trimmed, in ms. */\nconst TRAILING_KEEP_MS = 250;\n\nexport type VadEndReason = 'silence' | 'max' | 'flush';\n\nexport type VadEvent =\n /** Speech began; `leadMs` is how much of the recent past belongs to it. */\n | { type: 'start'; leadMs: number }\n /** The utterance ended; `trailingSilenceMs` of it is silence. */\n | { type: 'end'; reason: VadEndReason; trailingSilenceMs: number }\n /** `maxUtteranceMs` reached while speaking: end this one, start the next. */\n | { type: 'split' };\n\nexport interface VadStep {\n events: VadEvent[];\n /** 0 to 1, how loud this frame is against what counts as speech. */\n level: number;\n /** Currently inside an utterance. */\n speaking: boolean;\n}\n\nexport interface VadSegmenter {\n /** Feed the RMS energy (0 to 1) of the next frame of `frameMs`. */\n push(energy: number, frameMs?: number): VadStep;\n /** Finish: ends an utterance in progress (`end` with reason `flush`). */\n flush(): VadEvent[];\n readonly speaking: boolean;\n /** The learned noise floor (RMS). */\n readonly noiseFloor: number;\n /** The energy above which a frame counts as speech. */\n readonly threshold: number;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/** Resolve options against the defaults, clamping silly values. */\nexport function resolveVadOptions(\n options: VadOptions = {},\n): Required<VadOptions> {\n const pick = (value: number | undefined, fallback: number, min: number) =>\n typeof value === 'number' && Number.isFinite(value)\n ? Math.max(min, value)\n : fallback;\n return {\n silenceMs: pick(options.silenceMs, VAD_DEFAULTS.silenceMs, VAD_FRAME_MS),\n minSpeechMs: pick(options.minSpeechMs, VAD_DEFAULTS.minSpeechMs, 0),\n preRollMs: pick(options.preRollMs, VAD_DEFAULTS.preRollMs, 0),\n maxUtteranceMs: pick(\n options.maxUtteranceMs,\n VAD_DEFAULTS.maxUtteranceMs,\n 1000,\n ),\n sensitivity: clamp(\n typeof options.sensitivity === 'number' &&\n Number.isFinite(options.sensitivity)\n ? options.sensitivity\n : VAD_DEFAULTS.sensitivity,\n 0,\n 1,\n ),\n calibrateMs: pick(options.calibrateMs, VAD_DEFAULTS.calibrateMs, 0),\n };\n}\n\n/** Root-mean-square loudness of a block of samples (-1 to 1). */\nexport function frameEnergy(samples: ArrayLike<number>): number {\n const n = samples.length;\n if (n === 0) return 0;\n let sum = 0;\n for (let i = 0; i < n; i++) {\n const s = samples[i] as number;\n sum += s * s;\n }\n return Math.sqrt(sum / n);\n}\n\nexport function createVadSegmenter(options: VadOptions = {}): VadSegmenter {\n const o = resolveVadOptions(options);\n // Higher sensitivity: a lower multiple of the floor and a lower absolute minimum.\n const ratio = 6 - 4 * o.sensitivity;\n const absMin = 0.02 - 0.015 * o.sensitivity;\n\n let floor = DEFAULT_FLOOR;\n let calibratedMs = 0;\n const calibrationEnergies: number[] = [];\n let speaking = false;\n let candidateMs = 0;\n let silenceMs = 0;\n let utteranceMs = 0;\n\n const onThreshold = () => Math.max(floor * ratio, absMin);\n // Hysteresis: once speaking, only clearly lower energy counts as a pause.\n const offThreshold = () => Math.max(onThreshold() * 0.6, floor * 1.5);\n\n const reset = () => {\n speaking = false;\n candidateMs = 0;\n silenceMs = 0;\n utteranceMs = 0;\n };\n\n return {\n get speaking() {\n return speaking;\n },\n get noiseFloor() {\n return floor;\n },\n get threshold() {\n return onThreshold();\n },\n\n push(energy, frameMs = VAD_FRAME_MS) {\n const events: VadEvent[] = [];\n const e = Number.isFinite(energy) ? Math.max(0, energy) : 0;\n const level = clamp(e / (onThreshold() * 3), 0, 1);\n\n // Learn the room from the quietest frames of the opening window. Frames\n // inside an utterance never count, and the estimate is capped so speech\n // at t=0 cannot raise the floor to speech level. Detection stays live.\n const calibrating = calibratedMs < o.calibrateMs;\n if (calibrating) {\n calibratedMs += frameMs;\n if (!speaking) {\n calibrationEnergies.push(e);\n const sorted = [...calibrationEnergies].sort((a, b) => a - b);\n const p =\n sorted[\n Math.min(\n sorted.length - 1,\n Math.floor(sorted.length * CALIBRATION_PERCENTILE),\n )\n ] ?? DEFAULT_FLOOR;\n floor = clamp(p, MIN_FLOOR, CALIBRATION_FLOOR_CAP);\n }\n }\n\n if (!speaking) {\n if (e > onThreshold()) {\n candidateMs += frameMs;\n if (candidateMs >= o.minSpeechMs) {\n speaking = true;\n silenceMs = 0;\n utteranceMs = candidateMs;\n events.push({\n type: 'start',\n leadMs: o.preRollMs + candidateMs,\n });\n candidateMs = 0;\n }\n } else if (!calibrating) {\n candidateMs = 0;\n // Track the room slowly: down faster than up, so a hum that fades\n // is forgotten sooner than a noise that creeps in is accepted.\n const alpha = (frameMs / FLOOR_TRACK_MS) * (e < floor ? 4 : 1);\n floor = clamp(\n floor + (e - floor) * Math.min(1, alpha),\n MIN_FLOOR,\n MAX_FLOOR,\n );\n } else {\n candidateMs = 0;\n }\n return { events, level, speaking };\n }\n\n utteranceMs += frameMs;\n if (e > offThreshold()) {\n silenceMs = 0;\n } else {\n silenceMs += frameMs;\n }\n if (silenceMs >= o.silenceMs) {\n events.push({\n type: 'end',\n reason: 'silence',\n trailingSilenceMs: silenceMs,\n });\n reset();\n } else if (utteranceMs >= o.maxUtteranceMs) {\n events.push({ type: 'split' });\n utteranceMs = 0;\n silenceMs = 0;\n }\n return { events, level, speaking };\n },\n\n flush() {\n const events: VadEvent[] = [];\n if (speaking) {\n events.push({\n type: 'end',\n reason: 'flush',\n trailingSilenceMs: silenceMs,\n });\n }\n reset();\n return events;\n },\n };\n}\n\n/** One finished utterance. */\nexport interface VadUtterance {\n /** Mono samples (-1 to 1) at `sampleRate`. */\n pcm: Float32Array;\n sampleRate: number;\n durationMs: number;\n reason: VadEndReason;\n}\n\nexport interface UtteranceCollectorOptions extends VadOptions {\n /** Sample rate of the PCM fed in. */\n sampleRate: number;\n onUtterance: (utterance: VadUtterance) => void;\n /** An utterance began (`true`) or ended (`false`). */\n onSpeaking?: (speaking: boolean) => void;\n /** Loudness 0 to 1 for each frame, for a meter. */\n onLevel?: (level: number) => void;\n}\n\nexport interface UtteranceCollector {\n /** Feed any amount of mono PCM. */\n push(samples: Float32Array): void;\n /** Hand over an utterance in progress and reset. */\n flush(): void;\n /**\n * Drop an utterance in progress and all buffered audio (pre-roll included)\n * without emitting it. The learned noise floor is kept.\n */\n discard(): void;\n readonly speaking: boolean;\n readonly segmenter: VadSegmenter;\n}\n\nexport function createUtteranceCollector(\n options: UtteranceCollectorOptions,\n): UtteranceCollector {\n const o = resolveVadOptions(options);\n const segmenter = createVadSegmenter(o);\n const frameSize = Math.max(\n 1,\n Math.round((options.sampleRate * VAD_FRAME_MS) / 1000),\n );\n const frameMs = (frameSize / options.sampleRate) * 1000;\n // Past audio kept for the pre-roll, plus the frames that proved it was speech.\n const ringFrames = Math.ceil((o.preRollMs + o.minSpeechMs) / frameMs) + 1;\n const ring: Float32Array[] = [];\n let carry = new Float32Array(0);\n let utterance: Float32Array[] | null = null;\n let wasSpeaking = false;\n\n const emit = (\n frames: Float32Array[],\n reason: VadEndReason,\n trailMs: number,\n ) => {\n // Drop silence beyond a short tail; the model gains nothing from it.\n const dropFrames = Math.max(\n 0,\n Math.floor((trailMs - TRAILING_KEEP_MS) / frameMs),\n );\n const kept = frames.slice(0, Math.max(1, frames.length - dropFrames));\n let total = 0;\n for (const f of kept) total += f.length;\n const pcm = new Float32Array(total);\n let at = 0;\n for (const f of kept) {\n pcm.set(f, at);\n at += f.length;\n }\n options.onUtterance({\n pcm,\n sampleRate: options.sampleRate,\n durationMs: (total / options.sampleRate) * 1000,\n reason,\n });\n };\n\n const setSpeaking = (speaking: boolean) => {\n if (speaking === wasSpeaking) return;\n wasSpeaking = speaking;\n options.onSpeaking?.(speaking);\n };\n\n const handleFrame = (frame: Float32Array) => {\n const step = segmenter.push(frameEnergy(frame), frameMs);\n options.onLevel?.(step.level);\n ring.push(frame);\n if (ring.length > ringFrames) ring.shift();\n if (utterance) utterance.push(frame);\n for (const event of step.events) {\n if (event.type === 'start') {\n // The ring already holds this frame; the utterance starts with it, and\n // it is buffered from the very first frame of capture, so an utterance\n // that begins during calibration keeps its first syllables.\n utterance = [...ring];\n // Near capture start there is less past than the pre-roll asks for.\n // Transcribers do better with a little lead-in, so pad with silence.\n const wantLead = Math.ceil(o.preRollMs / frameMs);\n const haveLead = Math.max(\n 0,\n ring.length - Math.ceil(o.minSpeechMs / frameMs),\n );\n for (let i = haveLead; i < wantLead; i++) {\n utterance.unshift(new Float32Array(frameSize));\n }\n } else if (event.type === 'end') {\n const frames = utterance ?? [];\n utterance = null;\n ring.length = 0;\n if (frames.length > 0)\n emit(frames, event.reason, event.trailingSilenceMs);\n } else if (event.type === 'split') {\n const frames = utterance ?? [];\n utterance = [];\n if (frames.length > 0) emit(frames, 'max', 0);\n }\n }\n setSpeaking(segmenter.speaking);\n };\n\n return {\n get speaking() {\n return segmenter.speaking;\n },\n segmenter,\n push(samples) {\n let data = samples;\n if (carry.length > 0) {\n data = new Float32Array(carry.length + samples.length);\n data.set(carry, 0);\n data.set(samples, carry.length);\n }\n let at = 0;\n while (at + frameSize <= data.length) {\n // Copy: the caller reuses its buffer.\n handleFrame(data.slice(at, at + frameSize));\n at += frameSize;\n }\n carry = data.slice(at);\n },\n flush() {\n const events = segmenter.flush();\n for (const event of events) {\n if (event.type === 'end' && utterance && utterance.length > 0) {\n emit(utterance, 'flush', event.trailingSilenceMs);\n }\n }\n utterance = null;\n ring.length = 0;\n carry = new Float32Array(0);\n setSpeaking(false);\n options.onLevel?.(0);\n },\n discard() {\n segmenter.flush();\n utterance = null;\n ring.length = 0;\n carry = new Float32Array(0);\n setSpeaking(false);\n options.onLevel?.(0);\n },\n };\n}\n","/**\n * Pure, dependency-free 16-bit PCM WAV helpers. No Node APIs and no DOM: they\n * run in the browser, in Node and in workers. Exposed as\n * `@happyvertical/speech/pcm`.\n *\n * `parseWavPcm16` is strict because it reads untrusted bytes: every declared\n * size is checked against the buffer before it is trusted, nothing is\n * allocated from a declared (rather than present) size, and work is linear in\n * the input length.\n */\n\nimport { WavFormatError } from './errors.js';\n\nconst HEADER_BYTES = 44;\nconst MAX_UINT32 = 0xffff_ffff;\nconst FORMAT_PCM = 1;\nconst FORMAT_EXTENSIBLE = 0xfffe;\n/** `KSDATAFORMAT_SUBTYPE_PCM` minus its leading format tag: 00000000-0010-8000-00AA-00389B71. */\nconst PCM_GUID_TAIL = [\n 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b,\n 0x71,\n];\n\n/**\n * Converts float samples (-1..1) to 16-bit PCM (scale 32768, the inverse of\n * `pcm16ToFloat32`, so Int16 round-trips exactly). Clamps; NaN becomes 0.\n */\nexport function float32ToPcm16(samples: Float32Array): Int16Array {\n const out = new Int16Array(samples.length);\n for (let index = 0; index < samples.length; index += 1) {\n const value = samples[index];\n if (Number.isNaN(value)) continue; // stays 0\n const clamped = value > 1 ? 1 : value < -1 ? -1 : value;\n out[index] = Math.min(0x7fff, Math.round(clamped * 0x8000));\n }\n return out;\n}\n\n/** Converts 16-bit PCM to float samples in -1..1 (divides by 32768). */\nexport function pcm16ToFloat32(samples: Int16Array): Float32Array {\n const out = new Float32Array(samples.length);\n for (let index = 0; index < samples.length; index += 1) {\n out[index] = samples[index] / 0x8000;\n }\n return out;\n}\n\nfunction assertSampleRate(sampleRate: number, what: string): void {\n // byteRate = sampleRate * 2 must fit an unsigned 32-bit field.\n if (\n !Number.isInteger(sampleRate) ||\n sampleRate < 1 ||\n sampleRate * 2 > MAX_UINT32\n ) {\n throw new WavFormatError(\n 'invalid_argument',\n `${what} must be a positive integer sample rate (got ${String(sampleRate)})`,\n );\n }\n}\n\nfunction writeTag(view: DataView, offset: number, tag: string): void {\n for (let index = 0; index < tag.length; index += 1) {\n view.setUint8(offset + index, tag.charCodeAt(index));\n }\n}\n\n/**\n * Encodes mono samples as a canonical 44-byte-header 16-bit PCM WAV. Float\n * input is clamped and converted; `Int16Array` is written as is.\n */\nexport function encodeWavPcm16(\n samples: Float32Array | Int16Array,\n sampleRate: number,\n): Uint8Array {\n assertSampleRate(sampleRate, 'sampleRate');\n const dataBytes = samples.length * 2;\n if (dataBytes + HEADER_BYTES - 8 > MAX_UINT32) {\n throw new WavFormatError(\n 'invalid_argument',\n 'too many samples for a single WAV (4 GiB limit)',\n );\n }\n const pcm = samples instanceof Int16Array ? samples : float32ToPcm16(samples);\n const bytes = new Uint8Array(HEADER_BYTES + dataBytes);\n const view = new DataView(bytes.buffer);\n writeTag(view, 0, 'RIFF');\n view.setUint32(4, 36 + dataBytes, true);\n writeTag(view, 8, 'WAVE');\n writeTag(view, 12, 'fmt ');\n view.setUint32(16, 16, true);\n view.setUint16(20, FORMAT_PCM, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n writeTag(view, 36, 'data');\n view.setUint32(40, dataBytes, true);\n for (let index = 0; index < pcm.length; index += 1) {\n view.setInt16(HEADER_BYTES + index * 2, pcm[index], true);\n }\n return bytes;\n}\n\n/** Constraints `parseWavPcm16` enforces on the declared format. */\nexport interface WavExpectation {\n /** Required sample rate in Hz. */\n sampleRate?: number;\n /** Required channel count. */\n channels?: number;\n}\n\n/** A validated 16-bit PCM WAV. */\nexport interface ParsedWavPcm16 {\n sampleRate: number;\n channels: number;\n /** Frames (one sample per channel). */\n frames: number;\n durationMs: number;\n /** Interleaved samples, decoded to native endianness (a copy). */\n samples: Int16Array;\n /** The little-endian PCM bytes, a view into the input. */\n data: Uint8Array;\n}\n\nfunction fail(\n reason: ConstructorParameters<typeof WavFormatError>[0],\n message: string,\n): never {\n throw new WavFormatError(reason, message);\n}\n\nfunction tagAt(bytes: Uint8Array, offset: number): string {\n return String.fromCharCode(\n bytes[offset],\n bytes[offset + 1],\n bytes[offset + 2],\n bytes[offset + 3],\n );\n}\n\ninterface Fmt {\n channels: number;\n sampleRate: number;\n}\n\nfunction readFmt(\n view: DataView,\n bytes: Uint8Array,\n body: number,\n size: number,\n): Fmt {\n if (size < 16) {\n fail('malformed_chunk', `fmt chunk is ${size} bytes; at least 16 required`);\n }\n let format = view.getUint16(body, true);\n const channels = view.getUint16(body + 2, true);\n const sampleRate = view.getUint32(body + 4, true);\n const byteRate = view.getUint32(body + 8, true);\n const blockAlign = view.getUint16(body + 12, true);\n const bits = view.getUint16(body + 14, true);\n\n if (format === FORMAT_EXTENSIBLE) {\n if (size < 40) {\n fail(\n 'malformed_chunk',\n `WAVE_FORMAT_EXTENSIBLE fmt chunk is ${size} bytes; at least 40 required`,\n );\n }\n const cbSize = view.getUint16(body + 16, true);\n if (cbSize < 22 || 18 + cbSize > size) {\n fail(\n 'malformed_chunk',\n `WAVE_FORMAT_EXTENSIBLE extension (${cbSize} bytes) is under 22 or runs past its fmt chunk`,\n );\n }\n const validBits = view.getUint16(body + 18, true);\n if (validBits !== 0 && validBits !== 16) {\n fail(\n 'unsupported_bits',\n `WAVE_FORMAT_EXTENSIBLE valid bits is ${validBits}; only 16 supported`,\n );\n }\n const guid = body + 24;\n const subFormat = view.getUint16(guid, true);\n const tailMatches = PCM_GUID_TAIL.every(\n (byte, index) => bytes[guid + 2 + index] === byte,\n );\n if (subFormat !== FORMAT_PCM || !tailMatches) {\n fail('unsupported_format', 'WAVE_FORMAT_EXTENSIBLE subtype is not PCM');\n }\n format = FORMAT_PCM;\n }\n\n if (format !== FORMAT_PCM) {\n fail('unsupported_format', `WAV format tag ${format} is not PCM`);\n }\n if (bits !== 16) {\n fail(\n 'unsupported_bits',\n `WAV is ${bits}-bit; only 16-bit PCM is supported`,\n );\n }\n if (channels < 1) {\n fail('inconsistent_header', 'WAV declares zero channels');\n }\n if (sampleRate < 1) {\n fail('inconsistent_header', 'WAV declares a zero sample rate');\n }\n if (blockAlign !== channels * 2) {\n fail(\n 'inconsistent_header',\n `WAV block align ${blockAlign} is not channels x 2 (${channels * 2})`,\n );\n }\n if (byteRate !== sampleRate * blockAlign) {\n fail(\n 'inconsistent_header',\n 'WAV byte rate is not sample rate x block align',\n );\n }\n return { channels, sampleRate };\n}\n\n/**\n * Strictly parses a 16-bit PCM WAV. Rejects (with `WavFormatError`) anything\n * that is not a well-formed RIFF/WAVE with a `fmt ` chunk before its `data`\n * chunk, format tag 1 (or WAVE_FORMAT_EXTENSIBLE with the PCM subtype), 16\n * bits, a consistent header, and a declared data length that lies within the\n * buffer, is non-zero and is a whole number of frames. Nothing is clamped or\n * guessed. Every chunk inside the declared RIFF is bounds-checked, including\n * those after `data`; bytes beyond the RIFF are ignored.\n */\nexport function parseWavPcm16(\n bytes: Uint8Array,\n expect: WavExpectation = {},\n): ParsedWavPcm16 {\n if (\n bytes.length < 12 ||\n tagAt(bytes, 0) !== 'RIFF' ||\n tagAt(bytes, 8) !== 'WAVE'\n ) {\n fail('not_riff', 'not a RIFF/WAVE file');\n }\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n const riffEnd = 8 + view.getUint32(4, true);\n if (riffEnd < 12) {\n fail('malformed_chunk', 'RIFF size is too small');\n }\n if (riffEnd > bytes.length) {\n fail(\n 'truncated',\n `RIFF declares ${riffEnd} bytes but only ${bytes.length} are present`,\n );\n }\n\n let fmt: Fmt | undefined;\n let dataAt: number | undefined;\n let dataSize = 0;\n let offset = 12;\n while (offset < riffEnd) {\n if (offset + 8 > riffEnd) {\n fail('malformed_chunk', 'chunk header runs past the end of the RIFF');\n }\n const id = tagAt(bytes, offset);\n const size = view.getUint32(offset + 4, true);\n const body = offset + 8;\n if (body + size > riffEnd) {\n fail(\n 'truncated',\n `${JSON.stringify(id)} chunk declares ${size} bytes but only ${riffEnd - body} remain`,\n );\n }\n\n if (id === 'fmt ') {\n if (fmt) fail('duplicate_fmt', 'WAV has more than one fmt chunk');\n fmt = readFmt(view, bytes, body, size);\n if (\n expect.sampleRate !== undefined &&\n fmt.sampleRate !== expect.sampleRate\n ) {\n fail(\n 'rate_mismatch',\n `WAV is ${fmt.sampleRate} Hz; expected ${expect.sampleRate} Hz`,\n );\n }\n if (expect.channels !== undefined && fmt.channels !== expect.channels) {\n fail(\n 'channel_mismatch',\n `WAV has ${fmt.channels} channel(s); expected ${expect.channels}`,\n );\n }\n } else if (id === 'data') {\n if (dataAt !== undefined) {\n fail('malformed_chunk', 'WAV has more than one data chunk');\n }\n if (!fmt)\n fail('data_before_fmt', 'WAV data chunk precedes its fmt chunk');\n if (size === 0) fail('empty_data', 'WAV data chunk is empty');\n const frameBytes = fmt.channels * 2;\n if (size % frameBytes !== 0) {\n fail(\n 'misaligned_data',\n `WAV data (${size} bytes) is not a whole number of ${frameBytes}-byte frames`,\n );\n }\n dataAt = body;\n dataSize = size;\n }\n // RIFF chunks are word aligned: an odd size is followed by one pad byte,\n // which must also lie inside the RIFF.\n const next = body + size + (size % 2);\n if (next > riffEnd) {\n fail('truncated', `${JSON.stringify(id)} chunk is missing its pad byte`);\n }\n offset = next;\n }\n if (!fmt || dataAt === undefined) {\n return fail(\n fmt ? 'missing_data' : 'missing_fmt',\n fmt ? 'WAV has no data chunk' : 'WAV has no fmt chunk',\n );\n }\n // Every chunk inside the RIFF has now been bounds-checked; decode.\n const data = bytes.subarray(dataAt, dataAt + dataSize);\n const samples = new Int16Array(dataSize / 2);\n for (let index = 0; index < samples.length; index += 1) {\n samples[index] = view.getInt16(dataAt + index * 2, true);\n }\n const frames = dataSize / (fmt.channels * 2);\n return {\n sampleRate: fmt.sampleRate,\n channels: fmt.channels,\n frames,\n durationMs: Math.round((frames / fmt.sampleRate) * 1000),\n samples,\n data,\n };\n}\n"],"mappings":"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"}