/** * wake-word-manifest.ts, the PINNED wake-word classifier manifest. * * Same contract as the piper/whisper pins in ./manifest.ts: exact version, URL, * byte size and sha256 for every artifact, hosted at the ONE append-only * `voice-runtimes-v1` release tag with a `.sha256` sidecar. Nothing is * fetched without a matching checksum. * * WHY THIS FILE IS DATA AND NOTHING ELSE * * A better classifier is expected to replace the pin below (an accent-diverse * retrain is the known next one). Swapping it must be a one-entry change here, * add the new version to {@link WAKE_WORD_MODELS}, move * {@link DEFAULT_WAKE_WORD_MODEL_VERSION}, and nothing else. Consumers read the * default through {@link resolveWakeWordModel}, so no consumer holds a version, * URL, or checksum of its own. Old versions stay listed and stay fetchable, * because the hosted assets are append-only and are never deleted. * * The wake-word engine, config surface, provisioning flow and recovery * housekeeping read this pin, see `platform/voice/wake/`. Per-surface audio * capture and UI do not live in the SDK, because capture is genuinely * per-surface: the engine takes 16 kHz frames and returns detections. * * THE CLASSIFIER IS THE LAST STAGE OF A THREE-STAGE PIPELINE * * audio -> melspectrogram -> speech-embedding backbone -> this classifier * * It consumes speech EMBEDDINGS, not audio, so it cannot run alone. A runtime * must also provide the two front-end models. Source them from Google's own * Apache-2.0 `speech_embedding` TFHub distribution rather than redistributing * openWakeWord's copies, see {@link WAKE_WORD_FRONT_END_SOURCING}, so the * provenance traces to that Apache-2.0 grant directly. */ import type { VerifiedDownloadSpec } from './download-verified.js'; /** * How a runtime should obtain the two front-end models this classifier sits * behind. Stated here so a provisioning flow surfaces the sourcing rule rather * than restating it. * * The melspectrogram stage is an untrained, fixed DSP graph (STFT plus a mel * filterbank and window function) with no learned parameters. The embedding * stage is Google's `speech_embedding` model, which openWakeWord's own README * identifies as "provided by Google as a TFHub module under an Apache-2.0 * license". Taking both from Google's distribution keeps the provenance * unambiguous. */ export declare const WAKE_WORD_FRONT_END_SOURCING = "This classifier consumes speech embeddings, not audio: a runtime must also provide the melspectrogram and speech-embedding front-end models. Export them from Google's Apache-2.0 speech_embedding TFHub distribution rather than redistributing openWakeWord's copies, so their provenance traces to that Apache-2.0 grant. The melspectrogram stage is an untrained DSP graph (STFT + mel filterbank) with no learned parameters."; /** * The front end, as actually built. {@link WAKE_WORD_FRONT_END_SOURCING} states * the rule; this states what satisfying it produced. * * Both stages were re-sourced away from openWakeWord's copies, and both were * measured against them, because the pinned classifier was TRAINED against that * front end, if the front end shifts, every number in docs/wake-word-model.md * stops describing the running detector. */ export interface WakeWordFrontEndManifest { /** * Stage 1. Not a download: a fixed STFT + mel filterbank with no learned * parameters, computed in `platform/voice/wake/melspectrogram.ts`. Its * constants were recovered numerically from openWakeWord's own * `melspectrogram.onnx` initializers rather than chosen. */ readonly melspectrogram: { readonly computedInCode: true; /** Largest absolute deviation, in dB, from the reference graph's output. */ readonly maxAbsDeviationDb: number; /** Mel values compared to establish that deviation. */ readonly valuesCompared: number; }; /** Stage 2. Google's model, re-sourced from Google's own Apache-2.0 distribution. */ readonly embedding: { readonly version: string; readonly license: string; /** * Google's own distribution the weights came from. Recorded so provenance * traces to the Apache-2.0 grant directly, not through a third party. */ readonly sourceUrl: string; /** The attribution NOTICE, hosted beside the artifact and checksummed like it. */ readonly notice: VerifiedDownloadSpec; /** Our ONNX build of those weights, hosted on the append-only release tag. */ readonly download: VerifiedDownloadSpec; /** Input shape the pipeline feeds it: one 76-frame, 32-bin mel window. */ readonly inputDims: readonly number[]; /** Embedding width it emits per window. */ readonly outputDim: number; /** * Largest absolute difference, per output element, from openWakeWord's copy * of the same model. 0 means bit-exact on every element measured. */ readonly maxAbsDeviation: number; /** Inputs the deviation above was measured over. */ readonly inputsCompared: number; }; /** * End-to-end evidence: the largest change in the CLASSIFIER's score caused by * running the re-sourced front end instead of openWakeWord's, and whether any * detection decision changed as a result. */ readonly endToEnd: { readonly maxAbsScoreDeviation: number; readonly framesCompared: number; readonly decisionFlipsAtRecommendedThreshold: number; }; } /** * The pinned front end. The melspectrogram stage carries no URL because there * is nothing to download, removing that download is the point. */ export declare const WAKE_WORD_FRONT_END: WakeWordFrontEndManifest; /** * openWakeWord ships a default detection threshold of 0.5. For this phrase that * is too low, so every pinned model below carries its own measured * `recommendedThreshold` and callers must use it rather than the upstream * default. See {@link WakeWordModelManifest.recommendedThreshold}. */ export declare const OPENWAKEWORD_UPSTREAM_DEFAULT_THRESHOLD = 0.5; /** Measured detection quality at the recommended threshold. */ export interface WakeWordModelMeasurements { /** The threshold every figure here was measured at. */ readonly threshold: number; /** * Fraction of wake-phrase utterances detected, 0..1. * * SYNTHETIC ONLY, see {@link recallIsSyntheticOnly}. */ readonly recall: number; /** * Fraction of never-trained minimal-pair phrases ("hey good vibe check", * "hey goodbye vibes", …) that wrongly fire, 0..1. Lower is better. */ readonly minimalPairFalseAcceptRate: number; /** False accepts per hour on held-out real human speech never trained on. */ readonly falseAcceptsPerHourRealSpeech: number; /** * Always true today, and stated rather than hidden: no human recording of the * wake phrase exists yet, so recall is measured entirely on text-to-speech * output from a single VITS model, no real microphones, no real rooms, no * accents outside the synthesis model's distribution, no children, no * whispering, no speakerphone. The false-accept figures ARE measured on real * human speech. A human test pass is required before this ships as a default. */ readonly recallIsSyntheticOnly: boolean; } /** One pinned wake-word classifier, in both runtime formats. */ export interface WakeWordModelManifest { /** Wake phrase this classifier detects. */ readonly phrase: string; /** Manifest version of THIS artifact set, bumped per retrain, not per SDK release. */ readonly version: string; /** SPDX identifier of the grant the artifacts themselves ship under. */ readonly license: string; /** * The attribution NOTICE, hosted next to the artifacts. Several training * corpora are CC BY, which REQUIRES attribution, a deployment that * redistributes these artifacts must carry this NOTICE with them. It is * checksummed like any other asset so it cannot be silently swapped. */ readonly notice: VerifiedDownloadSpec; /** onnxruntime format (node/web). */ readonly onnx: VerifiedDownloadSpec; /** TensorFlow Lite format (mobile). Bit-identical decisions to the onnx twin. */ readonly tflite: VerifiedDownloadSpec; /** * The threshold to run at. NOT openWakeWord's 0.5, see * {@link OPENWAKEWORD_UPSTREAM_DEFAULT_THRESHOLD}. */ readonly recommendedThreshold: number; /** Measured quality at {@link recommendedThreshold}. */ readonly measurements: WakeWordModelMeasurements; /** One-line plain statement of what trained it, for a UI that describes the model. */ readonly provenance: string; } /** * Every pinned wake-word model, keyed by version. Append-only: an entry is * added for a new retrain and existing entries are never edited or removed, * because the hosted assets they point at are never re-uploaded or renamed. */ export declare const WAKE_WORD_MODELS: Readonly>; /** One measured operating point of the speech gate, on held-out frames. */ export interface WakeVadThresholdRow { /** `voice.wake.vadThreshold` value this row describes. */ readonly threshold: number; /** * Fraction of held-out SPEECH frames that pass the gate and reach the * classifier, 0..1. This is the number that decides whether a wake can fire at * all, so it is the one to read first: a gated speech frame is a wake that * cannot happen. */ readonly speechPassRate: number; /** * Fraction of held-out NON-SPEECH frames the gate stops, 0..1, classifier * inferences not run, which is the entire point of the stage. */ readonly noiseGateRate: number; } /** * The speech gate `voice.wake.vadThreshold` turns on: OUR speech/non-speech * head, trained by us, over the SAME pinned embedding the wake classifier sits * behind. * * WHY A HEAD RATHER THAN A SEPARATE VAD MODEL * * The front end (melspectrogram + {@link WAKE_WORD_FRONT_END}) already runs once * per 80 ms frame for the classifier. A head over that same 96-dimension * embedding costs a few thousand multiply-adds and NO extra front-end pass, and * provisions with artifacts the surface already has. A standalone VAD would add * its own front end, its own download, and its own provenance to keep honest. */ export interface WakeVadModelManifest { /** Manifest version of this head, bumped per retrain, not per SDK release. */ readonly version: string; /** SPDX identifier of the grant the artifact ships under. */ readonly license: string; /** Attribution NOTICE, hosted beside the artifacts and checksummed like them. */ readonly notice: VerifiedDownloadSpec; /** onnxruntime format (node/web). */ readonly onnx: VerifiedDownloadSpec; /** TensorFlow Lite format (mobile). */ readonly tflite: VerifiedDownloadSpec; /** The embedding manifest version this head was trained against. */ readonly frontEndVersion: string; /** Input the graph takes: one 96-dimension embedding frame. */ readonly inputDims: readonly number[]; /** Graph input/output names, recorded so a host can assert what it loaded. */ readonly inputName: string; readonly outputName: string; /** * The threshold to run at, measured. Read this rather than picking a round * number: the shipped `voice.wake.vadThreshold` default is 0 (gate off), and * this is what to set it to when turning the gate on. */ readonly recommendedThreshold: number; /** Measured behaviour across operating points, on held-out frames. */ readonly thresholds: readonly WakeVadThresholdRow[]; /** How the figures above were measured. */ readonly measurements: { /** Held-out frames scored. */ readonly evalFrames: number; /** How many of those were speech. */ readonly evalSpeechFrames: number; /** * Largest absolute difference between the onnx and tflite twins' outputs over * the frames compared, and how many detection decisions that changed at the * recommended threshold. 0 flips means the twins decide identically. */ readonly maxAbsTwinDeviation: number; readonly twinDecisionFlips: number; readonly twinFramesCompared: number; }; /** One-line plain statement of what trained it, for a UI that describes it. */ readonly provenance: string; } /** * The pinned speech gate. * * Hosted on the same append-only `voice-runtimes-v1` tag as every other voice * artifact, provisioned with the wake models, and verified by content before use. * * THE ASSETS LAND WITH THIS ROUND'S RELEASE. The byte counts and checksums below * are of the built artifacts and are what the upload must match; until the upload * happens a provision reports the gate as failed and `vadReady` false, while the * detector itself stays ready, which is why the gate is not part of * `WakeProvisionStatus.ready`. */ export declare const WAKE_VAD_MODEL: WakeVadModelManifest; /** * Total download size of the speech gate's artifacts (bytes), for an offer. * * Counts what a provision FETCHES, the onnx head and its NOTICE, and not the * tflite twin, which is pinned here for a runtime that cannot load onnx but is * not part of the plan, because nothing in this SDK loads it. The rule this obeys * is the one the classifier's twin was fixed to obey: a reported download size * has to describe the set of artifacts actually fetched, or `voice.wake.status` * quotes a figure for a download that never happens. The classifier's twin is * counted by {@link wakeWordProvisionBytes} because the plan fetches it; this one * is not counted because the plan does not. */ export declare function wakeVadProvisionBytes(vad?: WakeVadModelManifest): number; /** * The measured row for a configured threshold, or the nearest one below it, so a * surface can state what a chosen value actually does instead of guessing. */ export declare function resolveWakeVadThreshold(threshold: number, vad?: WakeVadModelManifest): WakeVadThresholdRow | null; /** * The version {@link resolveWakeWordModel} returns. Moving this line is the * whole of adopting a newer model. */ export declare const DEFAULT_WAKE_WORD_MODEL_VERSION = "1.0.0"; /** * The pinned model to use. Callers read the default through this rather than * indexing {@link WAKE_WORD_MODELS} with a version of their own, so a model * swap stays a one-line change in this file. */ export declare function resolveWakeWordModel(version?: string): WakeWordModelManifest | null; /** Total download size of a wake-word model's artifacts (bytes), for an offer. */ export declare function wakeWordProvisionBytes(model: WakeWordModelManifest): number; /** * Total download size of the front end's artifacts (bytes). * * A function beside {@link wakeWordProvisionBytes} rather than an addition at * each call site, and for the reason the front end's own NOTICE exposed: a * consumer summing `embedding.download.bytes` by hand quietly omitted the * attribution file, so the reported download size described a set of artifacts * that was not the set being fetched. The melspectrogram stage contributes * nothing, it is computed in code, which is the point of it. */ export declare function wakeWordFrontEndProvisionBytes(): number; //# sourceMappingURL=wake-word-manifest.d.ts.map