/** * push-to-talk.ts, the voice-input session both surfaces drive. * * This is the OTHER consumer of the capture primitive, and the one that has been * missing outright on the terminal: whisper is provisioned there and transcribes * on request, but nothing ever handed it audio, because nothing captured any. * * The state machine is small and identical on both surfaces, which is exactly why * it belongs here rather than in each of them: start (asking for the device is * its own visible phase, because a permission prompt takes real time), record, * stop, transcribe, done, with a device release on every path out, including the * failing ones. A microphone left open after a failed transcription is the bug * users notice and never report precisely. * * Transcription itself is injected. On both surfaces the audio goes to the * daemon's `voice.stt` verb rather than to a local library, so the SDK holds the * policy and the surface holds the call. */ import { type NoiseSuppressionFactory } from './noise-suppression.js'; import { AudioCaptureError, type AudioCaptureBackend, type AudioCaptureNoiseSuppression, type AudioCaptureOpener } from './types.js'; import { type CapturedUtterance } from './voice-input.js'; /** Where a voice-input session is. Mirrors what a surface renders. */ export type PushToTalkPhase = 'idle' | 'requesting' | 'recording' | 'stopping' | 'error'; export interface PushToTalkOptions { /** * Opens the device. Wrapped here so `voice.wake.noiseSuppression` reaches voice * input as well as wake detection, the row is shared, so the filter must be, * see {@link createNoiseSuppressingOpener}. A host passes its plain opener. */ readonly openCapture: AudioCaptureOpener; /** * Builds the suppression stage. Defaults to the embedded speexdsp filter; * injected so a test can drive the wiring deterministically. */ readonly createNoiseSuppression?: NoiseSuppressionFactory | undefined; /** Device, backend and suppression, from the shared `voice.wake.*` capture rows. */ readonly capture: { readonly device: string; readonly backend: AudioCaptureBackend; readonly noiseSuppression: AudioCaptureNoiseSuppression; readonly frameSamples: number; }; /** * Hard ceiling, from `voice.wake.captureMaxSeconds`. It applies to held-key * capture too: a key event that never arrives (a lost focus, a dropped * terminal) must not hold the microphone open indefinitely. 0 removes it, and * a held key becomes the only thing that ends the capture. */ readonly captureMaxSeconds: number; /** * Silence floor, from `voice.wake.silenceFloorRms`. Unset or 0 uses the fixed * {@link VOICE_INPUT_SILENCE_RMS}: push-to-talk has no pre-wake audio to * measure a room from, so there is nothing to adapt to here. It still decides * whether an utterance is reported as silent. */ readonly silenceFloorRms?: number | undefined; /** * How long a run above the floor must last to count as speech, from * `voice.wake.speechRetriggerMs`. The same row as post-wake capture, because * the microphone and the noises it picks up are the same ones; here it decides * whether a capture is reported as silent rather than when it ends. */ readonly speechRetriggerMs?: number | undefined; /** * Silence that ends capture on its own. 0, the push-to-talk default, leaves * stopping to the user, because someone holding a key through a pause has not * finished talking. */ readonly silenceStopMs?: number | undefined; readonly onPhaseChange?: ((phase: PushToTalkPhase) => void) | undefined; /** Capture ended itself (ceiling or silence) rather than being stopped. */ readonly onAutoStop?: ((utterance: CapturedUtterance) => void) | undefined; readonly onError?: ((error: AudioCaptureError) => void) | undefined; } /** * One press-to-talk capture. Reusable: `start` again after a `stop` or a failure. */ export declare class PushToTalkSession { #private; constructor(options: PushToTalkOptions); get phase(): PushToTalkPhase; /** What opened the device, for an indicator; null when nothing is open. */ get deviceLabel(): string | null; /** Milliseconds captured so far. */ get durationMs(): number; /** * Open the device and start recording. Rejects with an * {@link AudioCaptureError} so a surface can render the specific reason, no * recorder installed, permission refused, plain-http origin. */ start(): Promise; /** * Stop recording, release the device, and return what was captured. Returns * null when nothing was recording, so a released key with no press behind it is * a no-op rather than an error. */ stop(): Promise; /** Abandon the capture and release the device, keeping nothing. */ cancel(): Promise; } //# sourceMappingURL=push-to-talk.d.ts.map