@happyvertical/speech 0.110.1 → 0.111.1

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 (53) hide show
  1. package/AGENT.md +3 -0
  2. package/README.md +46 -3
  3. package/dist/adapters/local/audio.d.ts.map +1 -1
  4. package/dist/adapters/local/worker.d.ts +8 -5
  5. package/dist/adapters/local/worker.d.ts.map +1 -1
  6. package/dist/adapters/openai-compatible-transcriber.d.ts +3 -0
  7. package/dist/adapters/openai-compatible-transcriber.d.ts.map +1 -1
  8. package/dist/adapters/openai-compatible.d.ts +3 -0
  9. package/dist/adapters/openai-compatible.d.ts.map +1 -1
  10. package/dist/adapters/openai-realtime.d.ts +8 -0
  11. package/dist/adapters/openai-realtime.d.ts.map +1 -1
  12. package/dist/adapters/qwen3.d.ts +6 -0
  13. package/dist/adapters/qwen3.d.ts.map +1 -1
  14. package/dist/adapters/studio-server.d.ts +11 -0
  15. package/dist/adapters/studio-server.d.ts.map +1 -1
  16. package/dist/adapters/voxtral-realtime.d.ts +8 -0
  17. package/dist/adapters/voxtral-realtime.d.ts.map +1 -1
  18. package/dist/browser.js +2 -2
  19. package/dist/chunks/errors-CzsR_5dE.js +122 -0
  20. package/dist/chunks/errors-CzsR_5dE.js.map +1 -0
  21. package/dist/chunks/{registry-DFSF3WoM.js → registry-DhB2589P.js} +5 -5
  22. package/dist/chunks/registry-DhB2589P.js.map +1 -0
  23. package/dist/chunks/{wav-vzrhhZbu.js → wav-PxVZJX9V.js} +15 -5
  24. package/dist/chunks/{wav-vzrhhZbu.js.map → wav-PxVZJX9V.js.map} +1 -1
  25. package/dist/chunks/{websocket-YbAfY6YI.js → websocket-DLZLLyD9.js} +2 -2
  26. package/dist/chunks/{websocket-YbAfY6YI.js.map → websocket-DLZLLyD9.js.map} +1 -1
  27. package/dist/conversation-server.js +2 -2
  28. package/dist/conversation.js +1 -1
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +302 -20
  32. package/dist/index.js.map +1 -1
  33. package/dist/local.js +21 -12
  34. package/dist/local.js.map +1 -1
  35. package/dist/pcm.js +2 -2
  36. package/dist/shared/audio.d.ts.map +1 -1
  37. package/dist/shared/errors.d.ts +48 -1
  38. package/dist/shared/errors.d.ts.map +1 -1
  39. package/dist/shared/http.d.ts +3 -0
  40. package/dist/shared/http.d.ts.map +1 -1
  41. package/dist/shared/probe.d.ts +90 -0
  42. package/dist/shared/probe.d.ts.map +1 -0
  43. package/dist/shared/streaming-factory.d.ts.map +1 -1
  44. package/dist/shared/streaming-types.d.ts +10 -1
  45. package/dist/shared/streaming-types.d.ts.map +1 -1
  46. package/dist/shared/types.d.ts +14 -0
  47. package/dist/shared/types.d.ts.map +1 -1
  48. package/dist/shared/wav.d.ts +12 -1
  49. package/dist/shared/wav.d.ts.map +1 -1
  50. package/package.json +1 -1
  51. package/dist/chunks/errors-Cs0vX-mV.js +0 -64
  52. package/dist/chunks/errors-Cs0vX-mV.js.map +0 -1
  53. package/dist/chunks/registry-DFSF3WoM.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { o as WavFormatError } from "./errors-Cs0vX-mV.js";
1
+ import { l as WavFormatError } from "./errors-CzsR_5dE.js";
2
2
  //#region src/shared/resample.ts
3
3
  /**
4
4
  * Mono resampler: windowed-sinc (Hann, 16 zero crossings per side)
@@ -425,12 +425,16 @@ function readFmt(view, bytes, body, size) {
425
425
  * bits, a consistent header, and a declared data length that lies within the
426
426
  * buffer, is non-zero and is a whole number of frames. Nothing is clamped or
427
427
  * guessed. Every chunk inside the declared RIFF is bounds-checked, including
428
- * those after `data`; bytes beyond the RIFF are ignored.
428
+ * those after `data`; bytes beyond the RIFF are ignored. With
429
+ * `allowUnknownLength`, the `0xFFFFFFFF` streaming sentinel sizes are read as
430
+ * "to the end" (see {@link WavExpectation.allowUnknownLength}).
429
431
  */
430
432
  function parseWavPcm16(bytes, expect = {}) {
431
433
  if (bytes.length < 12 || tagAt(bytes, 0) !== "RIFF" || tagAt(bytes, 8) !== "WAVE") fail("not_riff", "not a RIFF/WAVE file");
432
434
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
433
- const riffEnd = 8 + view.getUint32(4, true);
435
+ const unknownLength = expect.allowUnknownLength === true;
436
+ const riffSize = view.getUint32(4, true);
437
+ const riffEnd = unknownLength && riffSize === MAX_UINT32 ? bytes.length : 8 + riffSize;
434
438
  if (riffEnd < 12) fail("malformed_chunk", "RIFF size is too small");
435
439
  if (riffEnd > bytes.length) fail("truncated", `RIFF declares ${riffEnd} bytes but only ${bytes.length} are present`);
436
440
  let fmt;
@@ -440,8 +444,13 @@ function parseWavPcm16(bytes, expect = {}) {
440
444
  while (offset < riffEnd) {
441
445
  if (offset + 8 > riffEnd) fail("malformed_chunk", "chunk header runs past the end of the RIFF");
442
446
  const id = tagAt(bytes, offset);
443
- const size = view.getUint32(offset + 4, true);
447
+ let size = view.getUint32(offset + 4, true);
444
448
  const body = offset + 8;
449
+ const streamedData = unknownLength && id === "data" && size === MAX_UINT32;
450
+ if (streamedData) {
451
+ const frameBytes = fmt ? fmt.channels * 2 : 2;
452
+ size = riffEnd - body - (riffEnd - body) % frameBytes;
453
+ }
445
454
  if (body + size > riffEnd) fail("truncated", `${JSON.stringify(id)} chunk declares ${size} bytes but only ${riffEnd - body} remain`);
446
455
  if (id === "fmt ") {
447
456
  if (fmt) fail("duplicate_fmt", "WAV has more than one fmt chunk");
@@ -456,6 +465,7 @@ function parseWavPcm16(bytes, expect = {}) {
456
465
  if (size % frameBytes !== 0) fail("misaligned_data", `WAV data (${size} bytes) is not a whole number of ${frameBytes}-byte frames`);
457
466
  dataAt = body;
458
467
  dataSize = size;
468
+ if (streamedData) break;
459
469
  }
460
470
  const next = body + size + size % 2;
461
471
  if (next > riffEnd) fail("truncated", `${JSON.stringify(id)} chunk is missing its pad byte`);
@@ -478,4 +488,4 @@ function parseWavPcm16(bytes, expect = {}) {
478
488
  //#endregion
479
489
  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 };
480
490
 
481
- //# sourceMappingURL=wav-vzrhhZbu.js.map
491
+ //# sourceMappingURL=wav-PxVZJX9V.js.map
@@ -1 +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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"}
1
+ {"version":3,"file":"wav-PxVZJX9V.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 * Accept a streamed WAV whose RIFF and/or `data` size is the unknown-length\n * sentinel `0xFFFFFFFF` (ffmpeg writing to a pipe, Kokoro-FastAPI\n * `response_format: \"wav\"`). A sentinel RIFF size runs to the end of the\n * buffer; a sentinel `data` size runs to the end of the RIFF, rounded down\n * to whole frames, and ends the chunk walk. Every other check still\n * applies. Off by default: leave it off for untrusted uploads.\n */\n allowUnknownLength?: boolean;\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. With\n * `allowUnknownLength`, the `0xFFFFFFFF` streaming sentinel sizes are read as\n * \"to the end\" (see {@link WavExpectation.allowUnknownLength}).\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 unknownLength = expect.allowUnknownLength === true;\n const riffSize = view.getUint32(4, true);\n const riffEnd =\n unknownLength && riffSize === MAX_UINT32 ? bytes.length : 8 + riffSize;\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 let size = view.getUint32(offset + 4, true);\n const body = offset + 8;\n // A streamed data chunk of unknown length is the rest of the RIFF, in\n // whole frames; nothing after it can be located, so it ends the walk.\n const streamedData = unknownLength && id === 'data' && size === MAX_UINT32;\n if (streamedData) {\n const frameBytes = fmt ? fmt.channels * 2 : 2;\n size = riffEnd - body - ((riffEnd - body) % frameBytes);\n }\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 if (streamedData) break;\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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"}
@@ -1,4 +1,4 @@
1
- import { r as SpeechConfigurationError } from "./errors-Cs0vX-mV.js";
1
+ import { i as SpeechConfigurationError } from "./errors-CzsR_5dE.js";
2
2
  //#region src/shared/websocket.ts
3
3
  /**
4
4
  * Minimal WebSocket surface used by streaming adapters. Browser, Node 22+
@@ -41,4 +41,4 @@ function messageDataToText(data) {
41
41
  //#endregion
42
42
  export { toWebSocketUrl as i, messageDataToText as n, resolveWebSocketFactory as r, isBrowserRuntime as t };
43
43
 
44
- //# sourceMappingURL=websocket-YbAfY6YI.js.map
44
+ //# sourceMappingURL=websocket-DLZLLyD9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-YbAfY6YI.js","names":[],"sources":["../../src/shared/websocket.ts"],"sourcesContent":["/**\n * Minimal WebSocket surface used by streaming adapters. Browser, Node 22+\n * (`globalThis.WebSocket`), Deno, Bun, and the `ws` package all satisfy it.\n */\n\nimport { SpeechConfigurationError } from './errors.js';\n\n/** `WebSocket.OPEN`. */\nexport const WEBSOCKET_OPEN = 1;\n\nexport interface SpeechWebSocketMessageEvent {\n data: unknown;\n}\n\nexport interface SpeechWebSocketCloseEvent {\n code?: number;\n reason?: string;\n}\n\n/** The subset of the WHATWG `WebSocket` interface the adapters use. */\nexport interface SpeechWebSocket {\n readonly readyState: number;\n readonly bufferedAmount: number;\n binaryType?: string;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: 'open', listener: (event: unknown) => void): void;\n addEventListener(\n type: 'message',\n listener: (event: SpeechWebSocketMessageEvent) => void,\n ): void;\n addEventListener(type: 'error', listener: (event: unknown) => void): void;\n addEventListener(\n type: 'close',\n listener: (event: SpeechWebSocketCloseEvent) => void,\n ): void;\n}\n\n/** Handshake details passed to a {@link SpeechWebSocketFactory}. */\nexport interface SpeechWebSocketInit {\n protocols: string[];\n /** Handshake headers; empty in browsers. */\n headers: Record<string, string>;\n}\n\nexport type SpeechWebSocketFactory = (\n url: string,\n init: SpeechWebSocketInit,\n) => SpeechWebSocket;\n\n/**\n * A WebSocket constructor. Standard constructors accept `protocols`; Node's\n * built-in WebSocket also accepts `{ protocols, headers }`.\n */\nexport type SpeechWebSocketConstructor = new (\n url: string,\n protocols?:\n | string\n | string[]\n | { protocols?: string[]; headers?: Record<string, string> },\n) => SpeechWebSocket;\n\n/** True in browsers and web workers, where API keys must never be used. */\nexport function isBrowserRuntime(): boolean {\n const scope = globalThis as {\n document?: unknown;\n WorkerGlobalScope?: unknown;\n process?: { versions?: { node?: string } };\n };\n if (scope.process?.versions?.node) {\n return false;\n }\n return (\n typeof scope.document !== 'undefined' ||\n typeof scope.WorkerGlobalScope !== 'undefined'\n );\n}\n\n/**\n * Resolves a socket factory from explicit options, falling back to\n * `globalThis.WebSocket`. Headers are only passed when present, so standard\n * constructors that cannot send them are never handed an options object.\n */\nexport function resolveWebSocketFactory(\n adapter: string,\n options: {\n createWebSocket?: SpeechWebSocketFactory;\n WebSocket?: SpeechWebSocketConstructor;\n },\n): SpeechWebSocketFactory {\n if (options.createWebSocket) {\n return options.createWebSocket;\n }\n\n const Ctor =\n options.WebSocket ??\n (globalThis as { WebSocket?: SpeechWebSocketConstructor }).WebSocket;\n if (!Ctor) {\n throw new SpeechConfigurationError(\n 'No WebSocket implementation available; pass `WebSocket` or `createWebSocket`',\n adapter,\n );\n }\n\n return (url, init) =>\n Object.keys(init.headers).length > 0\n ? new Ctor(url, { protocols: init.protocols, headers: init.headers })\n : new Ctor(url, init.protocols);\n}\n\n/** Converts `http(s)://` to `ws(s)://`; leaves `ws(s)://` unchanged. */\nexport function toWebSocketUrl(url: string, adapter: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n } else if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {\n throw new SpeechConfigurationError(\n `Unsupported streaming URL protocol: ${parsed.protocol}`,\n adapter,\n );\n }\n return parsed.toString();\n}\n\n/** Converts a message payload to text; returns `undefined` for unsupported data. */\nexport function messageDataToText(data: unknown): string | undefined {\n if (typeof data === 'string') {\n return data;\n }\n if (data instanceof ArrayBuffer) {\n return new TextDecoder().decode(data);\n }\n if (ArrayBuffer.isView(data)) {\n return new TextDecoder().decode(data);\n }\n return undefined;\n}\n"],"mappings":";;;;;;;AA+DA,SAAgB,mBAA4B;CAC1C,MAAM,QAAQ;CAKd,IAAI,MAAM,SAAS,UAAU,MAC3B,OAAO;CAET,OACE,OAAO,MAAM,aAAa,eAC1B,OAAO,MAAM,sBAAsB;AAEvC;;;;;;AAOA,SAAgB,wBACd,SACA,SAIwB;CACxB,IAAI,QAAQ,iBACV,OAAO,QAAQ;CAGjB,MAAM,OACJ,QAAQ,aACP,WAA0D;CAC7D,IAAI,CAAC,MACH,MAAM,IAAI,yBACR,gFACA,OACF;CAGF,QAAQ,KAAK,SACX,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,SAAS,IAC/B,IAAI,KAAK,KAAK;EAAE,WAAW,KAAK;EAAW,SAAS,KAAK;CAAQ,CAAC,IAClE,IAAI,KAAK,KAAK,KAAK,SAAS;AACpC;;AAGA,SAAgB,eAAe,KAAa,SAAyB;CACnE,MAAM,SAAS,IAAI,IAAI,GAAG;CAC1B,IAAI,OAAO,aAAa,SACtB,OAAO,WAAW;MACb,IAAI,OAAO,aAAa,UAC7B,OAAO,WAAW;MACb,IAAI,OAAO,aAAa,SAAS,OAAO,aAAa,QAC1D,MAAM,IAAI,yBACR,uCAAuC,OAAO,YAC9C,OACF;CAEF,OAAO,OAAO,SAAS;AACzB;;AAGA,SAAgB,kBAAkB,MAAmC;CACnE,IAAI,OAAO,SAAS,UAClB,OAAO;CAET,IAAI,gBAAgB,aAClB,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;CAEtC,IAAI,YAAY,OAAO,IAAI,GACzB,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;AAGxC"}
1
+ {"version":3,"file":"websocket-DLZLLyD9.js","names":[],"sources":["../../src/shared/websocket.ts"],"sourcesContent":["/**\n * Minimal WebSocket surface used by streaming adapters. Browser, Node 22+\n * (`globalThis.WebSocket`), Deno, Bun, and the `ws` package all satisfy it.\n */\n\nimport { SpeechConfigurationError } from './errors.js';\n\n/** `WebSocket.OPEN`. */\nexport const WEBSOCKET_OPEN = 1;\n\nexport interface SpeechWebSocketMessageEvent {\n data: unknown;\n}\n\nexport interface SpeechWebSocketCloseEvent {\n code?: number;\n reason?: string;\n}\n\n/** The subset of the WHATWG `WebSocket` interface the adapters use. */\nexport interface SpeechWebSocket {\n readonly readyState: number;\n readonly bufferedAmount: number;\n binaryType?: string;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: 'open', listener: (event: unknown) => void): void;\n addEventListener(\n type: 'message',\n listener: (event: SpeechWebSocketMessageEvent) => void,\n ): void;\n addEventListener(type: 'error', listener: (event: unknown) => void): void;\n addEventListener(\n type: 'close',\n listener: (event: SpeechWebSocketCloseEvent) => void,\n ): void;\n}\n\n/** Handshake details passed to a {@link SpeechWebSocketFactory}. */\nexport interface SpeechWebSocketInit {\n protocols: string[];\n /** Handshake headers; empty in browsers. */\n headers: Record<string, string>;\n}\n\nexport type SpeechWebSocketFactory = (\n url: string,\n init: SpeechWebSocketInit,\n) => SpeechWebSocket;\n\n/**\n * A WebSocket constructor. Standard constructors accept `protocols`; Node's\n * built-in WebSocket also accepts `{ protocols, headers }`.\n */\nexport type SpeechWebSocketConstructor = new (\n url: string,\n protocols?:\n | string\n | string[]\n | { protocols?: string[]; headers?: Record<string, string> },\n) => SpeechWebSocket;\n\n/** True in browsers and web workers, where API keys must never be used. */\nexport function isBrowserRuntime(): boolean {\n const scope = globalThis as {\n document?: unknown;\n WorkerGlobalScope?: unknown;\n process?: { versions?: { node?: string } };\n };\n if (scope.process?.versions?.node) {\n return false;\n }\n return (\n typeof scope.document !== 'undefined' ||\n typeof scope.WorkerGlobalScope !== 'undefined'\n );\n}\n\n/**\n * Resolves a socket factory from explicit options, falling back to\n * `globalThis.WebSocket`. Headers are only passed when present, so standard\n * constructors that cannot send them are never handed an options object.\n */\nexport function resolveWebSocketFactory(\n adapter: string,\n options: {\n createWebSocket?: SpeechWebSocketFactory;\n WebSocket?: SpeechWebSocketConstructor;\n },\n): SpeechWebSocketFactory {\n if (options.createWebSocket) {\n return options.createWebSocket;\n }\n\n const Ctor =\n options.WebSocket ??\n (globalThis as { WebSocket?: SpeechWebSocketConstructor }).WebSocket;\n if (!Ctor) {\n throw new SpeechConfigurationError(\n 'No WebSocket implementation available; pass `WebSocket` or `createWebSocket`',\n adapter,\n );\n }\n\n return (url, init) =>\n Object.keys(init.headers).length > 0\n ? new Ctor(url, { protocols: init.protocols, headers: init.headers })\n : new Ctor(url, init.protocols);\n}\n\n/** Converts `http(s)://` to `ws(s)://`; leaves `ws(s)://` unchanged. */\nexport function toWebSocketUrl(url: string, adapter: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n } else if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') {\n throw new SpeechConfigurationError(\n `Unsupported streaming URL protocol: ${parsed.protocol}`,\n adapter,\n );\n }\n return parsed.toString();\n}\n\n/** Converts a message payload to text; returns `undefined` for unsupported data. */\nexport function messageDataToText(data: unknown): string | undefined {\n if (typeof data === 'string') {\n return data;\n }\n if (data instanceof ArrayBuffer) {\n return new TextDecoder().decode(data);\n }\n if (ArrayBuffer.isView(data)) {\n return new TextDecoder().decode(data);\n }\n return undefined;\n}\n"],"mappings":";;;;;;;AA+DA,SAAgB,mBAA4B;CAC1C,MAAM,QAAQ;CAKd,IAAI,MAAM,SAAS,UAAU,MAC3B,OAAO;CAET,OACE,OAAO,MAAM,aAAa,eAC1B,OAAO,MAAM,sBAAsB;AAEvC;;;;;;AAOA,SAAgB,wBACd,SACA,SAIwB;CACxB,IAAI,QAAQ,iBACV,OAAO,QAAQ;CAGjB,MAAM,OACJ,QAAQ,aACP,WAA0D;CAC7D,IAAI,CAAC,MACH,MAAM,IAAI,yBACR,gFACA,OACF;CAGF,QAAQ,KAAK,SACX,OAAO,KAAK,KAAK,OAAO,CAAC,CAAC,SAAS,IAC/B,IAAI,KAAK,KAAK;EAAE,WAAW,KAAK;EAAW,SAAS,KAAK;CAAQ,CAAC,IAClE,IAAI,KAAK,KAAK,KAAK,SAAS;AACpC;;AAGA,SAAgB,eAAe,KAAa,SAAyB;CACnE,MAAM,SAAS,IAAI,IAAI,GAAG;CAC1B,IAAI,OAAO,aAAa,SACtB,OAAO,WAAW;MACb,IAAI,OAAO,aAAa,UAC7B,OAAO,WAAW;MACb,IAAI,OAAO,aAAa,SAAS,OAAO,aAAa,QAC1D,MAAM,IAAI,yBACR,uCAAuC,OAAO,YAC9C,OACF;CAEF,OAAO,OAAO,SAAS;AACzB;;AAGA,SAAgB,kBAAkB,MAAmC;CACnE,IAAI,OAAO,SAAS,UAClB,OAAO;CAET,IAAI,gBAAgB,aAClB,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;CAEtC,IAAI,YAAY,OAAO,IAAI,GACzB,OAAO,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;AAGxC"}
@@ -1,5 +1,5 @@
1
- import { a as SpeechProviderError, r as SpeechConfigurationError } from "./chunks/errors-Cs0vX-mV.js";
2
- import { t as isBrowserRuntime } from "./chunks/websocket-YbAfY6YI.js";
1
+ import { i as SpeechConfigurationError, s as SpeechProviderError } from "./chunks/errors-CzsR_5dE.js";
2
+ import { t as isBrowserRuntime } from "./chunks/websocket-DLZLLyD9.js";
3
3
  //#region src/conversation-server.ts
4
4
  /** Server-only OpenAI conversational call lifecycle. Hosts own authorization and admission policy. */
5
5
  /** Build the GA speech-to-speech session config. Transcription is an optional side channel. */
@@ -1,4 +1,4 @@
1
- import { a as SpeechProviderError, r as SpeechConfigurationError } from "./chunks/errors-Cs0vX-mV.js";
1
+ import { i as SpeechConfigurationError, s as SpeechProviderError } from "./chunks/errors-CzsR_5dE.js";
2
2
  //#region src/conversation/openai-webrtc.ts
3
3
  var ADAPTER = "openai-conversation";
4
4
  var MAX_PENDING_CANCELLATIONS = 32;
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from './shared/errors.js';
29
29
  export { getAvailableSpeechAdapters, getSpeech, getSpeechSynthesizer, getTranscriber, type SpeechFactoryContext, } from './shared/factory.js';
30
30
  export { type TranscriberInputFormat, transcriberInputFormat, } from './shared/input-format.js';
31
31
  export { audioSecondsForBytes, bytesPerSecond, unwrapRawAudio, } from './shared/pcm.js';
32
+ export { DEFAULT_SPEECH_PROBE_TIMEOUT_MS, type SpeechProbeCheck, type SpeechProbeFailureReason, type SpeechProbeOptions, type SpeechProbeResult, } from './shared/probe.js';
32
33
  export { type RealtimeConnection, type RealtimeProtocol, type RealtimeProtocolEvent, type RealtimeSessionConfig, type RealtimeSessionInit, RealtimeTranscriptionSession, } from './shared/realtime-session.js';
33
34
  export { CLIENT_SECRET_TTL_RANGE, type CreateStreamingClientSecretOptions, createStreamingClientSecret, DEFAULT_CLIENT_SECRET_TTL_SECONDS, resolveClientSecretUrl, type StreamingClientSecretContext, type StreamingClientSecretResult, } from './shared/streaming-client-secret.js';
34
35
  export { getStreamingTranscriber, isStreamingTranscriberType, parseMaxTurnSeconds, STREAMING_TRANSCRIBER_ENV_KEYS, STREAMING_TRANSCRIBER_TYPES, type StreamingFactoryContext, type StreamingTranscriberWrapperOptions, wrapStreamingTranscriber, } from './shared/streaming-factory.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}