@burdenoff/fe-libs 2026.1005.1 → 2026.1005.3
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.
- package/dist/chrome/ContextSwitcher.d.ts +2 -0
- package/dist/chrome/ContextSwitcher.d.ts.map +1 -1
- package/dist/chrome/ContextSwitcher.js +70 -67
- package/dist/chrome/contextSwitcher/ProjectList.d.ts +1 -0
- package/dist/chrome/contextSwitcher/ProjectList.d.ts.map +1 -1
- package/dist/chrome/contextSwitcher/ProjectList.js +18 -5
- package/dist/node_modules/fflate/esm/browser.js +3 -3
- package/dist/node_modules/hast-util-to-jsx-runtime/lib/index.js +51 -51
- package/dist/node_modules/pdfjs-dist/build/pdf.js +2268 -1656
- package/dist/node_modules/pdfjs-dist/build/pdf.worker.min.js +8719 -7535
- package/dist/shared/assistant/attachments.js +1 -0
- package/dist/shared/assistant/i18nManifest.d.ts +4 -0
- package/dist/shared/assistant/i18nManifest.d.ts.map +1 -1
- package/dist/shared/assistant/i18nManifest.js +4 -0
- package/dist/shared/assistant/images.js +1 -1
- package/dist/shared/assistant/index.d.ts +16 -0
- package/dist/shared/assistant/index.d.ts.map +1 -1
- package/dist/shared/assistant/runtime.js +1 -0
- package/dist/shared/assistant/speech/browserSpeechEngine.d.ts.map +1 -1
- package/dist/shared/assistant/speech/browserSpeechEngine.js +46 -45
- package/dist/shared/assistant/speech/createSpeechEngine.js +1 -1
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts.map +1 -1
- package/dist/shared/assistant/speech/hostedTtsSpeechEngine.js +80 -79
- package/dist/shared/assistant/store.d.ts +3 -0
- package/dist/shared/assistant/store.d.ts.map +1 -1
- package/dist/shared/assistant/store.js +43 -35
- package/dist/shared/assistant/ui/AssistantChatArea.d.ts.map +1 -1
- package/dist/shared/assistant/ui/AssistantChatArea.js +1076 -1074
- package/dist/shared/assistant/ui/AssistantMoreMenu.d.ts.map +1 -1
- package/dist/shared/assistant/ui/AssistantMoreMenu.js +59 -57
- package/dist/shared/assistant/ui/composer/AssistantHandsFreeInterruptToggle.d.ts +17 -0
- package/dist/shared/assistant/ui/composer/AssistantHandsFreeInterruptToggle.d.ts.map +1 -0
- package/dist/shared/assistant/ui/composer/AssistantHandsFreeInterruptToggle.js +34 -0
- package/dist/shared/assistant/ui/useVoiceSession.d.ts +29 -2
- package/dist/shared/assistant/ui/useVoiceSession.d.ts.map +1 -1
- package/dist/shared/assistant/ui/useVoiceSession.js +247 -213
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +2 -0
- package/dist/shared/assistant/uiActions/deixis.js +1 -1
- package/dist/shared/assistant/voice/handsfree/audioTrackTransport.d.ts +33 -0
- package/dist/shared/assistant/voice/handsfree/audioTrackTransport.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/audioTrackTransport.js +98 -0
- package/dist/shared/assistant/voice/handsfree/backendStreamTransport.d.ts +34 -0
- package/dist/shared/assistant/voice/handsfree/backendStreamTransport.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/backendStreamTransport.js +130 -0
- package/dist/shared/assistant/voice/handsfree/bargeIn.d.ts +43 -0
- package/dist/shared/assistant/voice/handsfree/bargeIn.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/bargeIn.js +32 -0
- package/dist/shared/assistant/voice/handsfree/captureWorklet.d.ts +47 -0
- package/dist/shared/assistant/voice/handsfree/captureWorklet.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/captureWorklet.js +85 -0
- package/dist/shared/assistant/voice/handsfree/echoGate.d.ts +140 -0
- package/dist/shared/assistant/voice/handsfree/echoGate.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/echoGate.js +198 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeController.d.ts +77 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeController.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeController.js +126 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeRuntime.d.ts +15 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeRuntime.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeRuntime.js +78 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeSetting.d.ts +26 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeSetting.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/handsFreeSetting.js +25 -0
- package/dist/shared/assistant/voice/handsfree/micCapture.d.ts +21 -0
- package/dist/shared/assistant/voice/handsfree/micCapture.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/micCapture.js +73 -0
- package/dist/shared/assistant/voice/handsfree/playbackReference.d.ts +38 -0
- package/dist/shared/assistant/voice/handsfree/playbackReference.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/playbackReference.js +55 -0
- package/dist/shared/assistant/voice/handsfree/preRollRing.d.ts +32 -0
- package/dist/shared/assistant/voice/handsfree/preRollRing.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/preRollRing.js +34 -0
- package/dist/shared/assistant/voice/handsfree/sttTransport.d.ts +94 -0
- package/dist/shared/assistant/voice/handsfree/sttTransport.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/sttTransport.js +27 -0
- package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.d.ts +33 -0
- package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/useHandsFreeInterrupt.js +70 -0
- package/dist/shared/assistant/voice/handsfree/vad.d.ts +55 -0
- package/dist/shared/assistant/voice/handsfree/vad.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/vad.js +41 -0
- package/dist/shared/assistant/voice/handsfree/webSpeechTransport.d.ts +3 -0
- package/dist/shared/assistant/voice/handsfree/webSpeechTransport.d.ts.map +1 -0
- package/dist/shared/assistant/voice/handsfree/webSpeechTransport.js +16 -0
- package/dist/shared/assistant/voice/voiceSession.d.ts +9 -0
- package/dist/shared/assistant/voice/voiceSession.d.ts.map +1 -1
- package/dist/shared/assistant/voice/voiceSession.js +2 -2
- package/dist/shared/assistant/voice/voiceTranscript.js +14 -14
- package/dist/shared/config/gatewayUrls.js +2 -2
- package/dist/shared/graphql/workspaceToken.d.ts +1 -0
- package/dist/shared/graphql/workspaceToken.d.ts.map +1 -1
- package/dist/shared/graphql/workspaceToken.js +18 -14
- package/dist/shared/plugins/iframe-mount/client/message-bus.js +1 -0
- package/dist/shared/plugins/iframe-mount/client/rpc.js +1 -0
- package/dist/shared/providers/shell/ActiveContextProvider.d.ts.map +1 -1
- package/dist/shared/providers/shell/ActiveContextProvider.js +165 -144
- package/dist/shared/providers/shell/AuthTokenProvider.d.ts.map +1 -1
- package/dist/shared/providers/shell/AuthTokenProvider.js +51 -46
- package/dist/shared/providers/shell/WorkspaceWideSelection.d.ts +10 -0
- package/dist/shared/providers/shell/WorkspaceWideSelection.d.ts.map +1 -0
- package/dist/shared/providers/shell/WorkspaceWideSelection.js +37 -0
- package/dist/shared/providers/shell/index.d.ts +2 -0
- package/dist/shared/providers/shell/index.d.ts.map +1 -1
- package/dist/shared/tours/TourTooltip.js +8 -8
- package/dist/shared/utils/format.js +1 -1
- package/dist/shared/utils/gatewayFetchRewrite.js +1 -1
- package/dist/shared-assistant.js +218 -202
- package/dist/shared-providers-shell.js +25 -24
- package/package.json +18 -8
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/handsfree/audioTrackTransport.ts
|
|
2
|
+
var e = .3, t = 1500;
|
|
3
|
+
function n(t) {
|
|
4
|
+
let n = t.now ?? (() => Date.now()), r = null, i = null, a = null, o = null, s = "en-US", c = null, l = !1, u = null, d = 0, f = null, p = (e) => {
|
|
5
|
+
!i || !a || (i.gain.cancelScheduledValues(a.currentTime), i.gain.setTargetAtTime(e, a.currentTime, .005));
|
|
6
|
+
}, m = () => {
|
|
7
|
+
if (!l || !c) return;
|
|
8
|
+
let e = t.createRecognition();
|
|
9
|
+
if (!e) {
|
|
10
|
+
o?.onError({
|
|
11
|
+
code: "unavailable",
|
|
12
|
+
retryable: !1
|
|
13
|
+
});
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
r = e, e.lang = s, e.continuous = !0, e.interimResults = !0, e.onresult = (e) => {
|
|
17
|
+
let t = u ?? (f && n() - d <= 1500 ? f : null);
|
|
18
|
+
if (t) for (let n = e.resultIndex; n < e.results.length; n += 1) {
|
|
19
|
+
let r = e.results[n], i = r?.[0]?.transcript?.trim() ?? "";
|
|
20
|
+
!r || !i || o?.onTranscript({
|
|
21
|
+
text: i,
|
|
22
|
+
final: r.isFinal,
|
|
23
|
+
utteranceId: t.utteranceId,
|
|
24
|
+
epoch: t.epoch,
|
|
25
|
+
language: s
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}, e.onerror = (e) => {
|
|
29
|
+
(e.error === "not-allowed" || e.error === "service-not-allowed") && (l = !1, o?.onError({
|
|
30
|
+
code: "not-allowed",
|
|
31
|
+
retryable: !1
|
|
32
|
+
}));
|
|
33
|
+
}, e.onend = () => {
|
|
34
|
+
l && r === e && m();
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
e.start(c);
|
|
38
|
+
} catch {
|
|
39
|
+
o?.onError({
|
|
40
|
+
code: "unavailable",
|
|
41
|
+
retryable: !1
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
kind: "audio-track",
|
|
47
|
+
recognitionOwner: "transport",
|
|
48
|
+
keepsOpeningWords: !0,
|
|
49
|
+
async start(t, n, r) {
|
|
50
|
+
if (!t) {
|
|
51
|
+
r.onError({
|
|
52
|
+
code: "unavailable",
|
|
53
|
+
retryable: !1
|
|
54
|
+
});
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
o = r, s = n, a = t.context;
|
|
58
|
+
let u = t.context.createDelay(1);
|
|
59
|
+
u.delayTime.value = e, i = t.context.createGain(), i.gain.value = 0;
|
|
60
|
+
let d = t.context.createMediaStreamDestination();
|
|
61
|
+
t.micSource.connect(u), u.connect(i), i.connect(d), c = d.stream.getAudioTracks()[0] ?? null, l = !0, m();
|
|
62
|
+
},
|
|
63
|
+
openUtterance(e) {
|
|
64
|
+
u = {
|
|
65
|
+
utteranceId: e.utteranceId,
|
|
66
|
+
epoch: e.epoch
|
|
67
|
+
}, f = u, p(1);
|
|
68
|
+
},
|
|
69
|
+
pushFrame() {},
|
|
70
|
+
closeUtterance(e) {
|
|
71
|
+
if (u?.utteranceId !== e) return;
|
|
72
|
+
d = n(), u = null;
|
|
73
|
+
let t = e;
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
u === null && f?.utteranceId === t && p(0);
|
|
76
|
+
}, 400);
|
|
77
|
+
},
|
|
78
|
+
cancelUtterance(e) {
|
|
79
|
+
u?.utteranceId === e && (u = null), f?.utteranceId === e && (f = null), p(0);
|
|
80
|
+
},
|
|
81
|
+
stop() {
|
|
82
|
+
l = !1, u = null, f = null;
|
|
83
|
+
let e = r;
|
|
84
|
+
if (r = null, e) {
|
|
85
|
+
e.onresult = null, e.onend = null, e.onerror = null;
|
|
86
|
+
try {
|
|
87
|
+
e.abort();
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
i?.disconnect();
|
|
92
|
+
} catch {}
|
|
93
|
+
c?.stop(), c = null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { e as BRANCH_DELAY_S, t as TRAILING_RESULT_MS, n as createAudioTrackTransport };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SttErrorCode, SttTransport } from './sttTransport';
|
|
2
|
+
/** What the transport needs of a WebSocket; `handsFreeRuntime` adapts the real one. */
|
|
3
|
+
export interface SocketLike {
|
|
4
|
+
readonly readyState: number;
|
|
5
|
+
send(data: string | ArrayBufferLike | ArrayBufferView): void;
|
|
6
|
+
close(code?: number): void;
|
|
7
|
+
listen(handlers: SocketHandlers): void;
|
|
8
|
+
}
|
|
9
|
+
export interface SocketHandlers {
|
|
10
|
+
open(): void;
|
|
11
|
+
message(data: unknown): void;
|
|
12
|
+
close(code: number): void;
|
|
13
|
+
error(): void;
|
|
14
|
+
}
|
|
15
|
+
export interface StreamTicket {
|
|
16
|
+
ticket: string;
|
|
17
|
+
wsUrl: string;
|
|
18
|
+
}
|
|
19
|
+
export interface BackendStreamDeps {
|
|
20
|
+
fetchTicket: () => Promise<StreamTicket>;
|
|
21
|
+
createSocket: (url: string, protocols: string[]) => SocketLike;
|
|
22
|
+
/** Languages sent with each utterance: the user's input language, then en-IN. */
|
|
23
|
+
languages: (language: string) => string[];
|
|
24
|
+
}
|
|
25
|
+
/** The gateway's frame cap is 8 KB; stay well under it. */
|
|
26
|
+
export declare const MAX_SEND_BYTES = 6400;
|
|
27
|
+
export declare const CLOSE_CODE_ERRORS: Readonly<Record<number, {
|
|
28
|
+
code: SttErrorCode;
|
|
29
|
+
retryable: boolean;
|
|
30
|
+
}>>;
|
|
31
|
+
/** Concatenate frames into chunks of at most `MAX_SEND_BYTES`. */
|
|
32
|
+
export declare function chunkFrames(frames: readonly Int16Array[]): Int16Array[];
|
|
33
|
+
export declare function createBackendStreamTransport(deps: BackendStreamDeps): SttTransport;
|
|
34
|
+
//# sourceMappingURL=backendStreamTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backendStreamTransport.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/voice/handsfree/backendStreamTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAEV,YAAY,EACZ,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,eAAe,GAAG,IAAI,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IACzC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,UAAU,CAAC;IAC/D,iFAAiF;IACjF,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAC3C;AAED,2DAA2D;AAC3D,eAAO,MAAM,cAAc,OAAO,CAAC;AAGnC,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CACtC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAO3D,CAAC;AASF,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAgBvE;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,iBAAiB,GACtB,YAAY,CAyId"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/handsfree/backendStreamTransport.ts
|
|
2
|
+
var e = 6400, t = 1, n = {
|
|
3
|
+
4401: {
|
|
4
|
+
code: "auth",
|
|
5
|
+
retryable: !1
|
|
6
|
+
},
|
|
7
|
+
4408: {
|
|
8
|
+
code: "closed",
|
|
9
|
+
retryable: !0
|
|
10
|
+
},
|
|
11
|
+
4413: {
|
|
12
|
+
code: "upstream",
|
|
13
|
+
retryable: !1
|
|
14
|
+
},
|
|
15
|
+
4429: {
|
|
16
|
+
code: "quota",
|
|
17
|
+
retryable: !1
|
|
18
|
+
},
|
|
19
|
+
4500: {
|
|
20
|
+
code: "upstream",
|
|
21
|
+
retryable: !0
|
|
22
|
+
}
|
|
23
|
+
}, r = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : null;
|
|
24
|
+
function i(t) {
|
|
25
|
+
let n = Math.floor(e / 2), r = [], i = [], a = () => {
|
|
26
|
+
i.length > 0 && r.push(Int16Array.from(i)), i = [];
|
|
27
|
+
};
|
|
28
|
+
for (let e of t) for (let t of e) i.push(t), i.length === n && a();
|
|
29
|
+
return a(), r;
|
|
30
|
+
}
|
|
31
|
+
function a(e) {
|
|
32
|
+
let a = null, o = null, s = "en-IN", c = null, l = null, u = null, d = (e, t) => {
|
|
33
|
+
o?.onError({
|
|
34
|
+
code: e,
|
|
35
|
+
retryable: t
|
|
36
|
+
});
|
|
37
|
+
}, f = () => new Promise((t) => {
|
|
38
|
+
e.fetchTicket().then(({ ticket: i, wsUrl: c }) => {
|
|
39
|
+
let f = e.createSocket(c, [`bearer.${i}`]);
|
|
40
|
+
a = f, f.listen({
|
|
41
|
+
open: () => t(!0),
|
|
42
|
+
error: () => t(!1),
|
|
43
|
+
close: (e) => {
|
|
44
|
+
a === f && (a = null), t(!1);
|
|
45
|
+
let r = n[e];
|
|
46
|
+
r ? d(r.code, r.retryable) : l && d("closed", !0);
|
|
47
|
+
},
|
|
48
|
+
message: (e) => {
|
|
49
|
+
if (typeof e != "string") return;
|
|
50
|
+
let t;
|
|
51
|
+
try {
|
|
52
|
+
t = r(JSON.parse(e));
|
|
53
|
+
} catch {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (t) if (t.t === "partial" || t.t === "final") {
|
|
57
|
+
if (typeof t.text != "string" || typeof t.utteranceId != "string" || typeof t.epoch != "number") return;
|
|
58
|
+
t.t === "final" && u === t.utteranceId && (u = null), o?.onTranscript({
|
|
59
|
+
text: t.text,
|
|
60
|
+
final: t.t === "final",
|
|
61
|
+
utteranceId: t.utteranceId,
|
|
62
|
+
epoch: t.epoch,
|
|
63
|
+
language: typeof t.lang == "string" ? t.lang : s
|
|
64
|
+
});
|
|
65
|
+
} else t.t === "error" && d(t.code === "quota" ? "quota" : t.code === "auth" ? "auth" : "upstream", t.retryable === !0);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}).catch(() => {
|
|
69
|
+
d("auth", !1), t(!1);
|
|
70
|
+
});
|
|
71
|
+
}), p = (e) => {
|
|
72
|
+
a && a.readyState === t && a.send(e);
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
kind: "backend-stream",
|
|
76
|
+
recognitionOwner: "transport",
|
|
77
|
+
keepsOpeningWords: !0,
|
|
78
|
+
async start(e, t, n) {
|
|
79
|
+
o = n, s = t, c = f(), await c || d("unavailable", !1);
|
|
80
|
+
},
|
|
81
|
+
openUtterance(t) {
|
|
82
|
+
l = {
|
|
83
|
+
utteranceId: t.utteranceId,
|
|
84
|
+
epoch: t.epoch
|
|
85
|
+
};
|
|
86
|
+
let n = t.preRoll;
|
|
87
|
+
(c ?? Promise.resolve(!1)).then((r) => {
|
|
88
|
+
if (!(!r || l?.utteranceId !== t.utteranceId)) {
|
|
89
|
+
p(JSON.stringify({
|
|
90
|
+
t: "config",
|
|
91
|
+
v: 1,
|
|
92
|
+
utteranceId: t.utteranceId,
|
|
93
|
+
epoch: t.epoch,
|
|
94
|
+
langs: e.languages(s),
|
|
95
|
+
sampleRate: 16e3,
|
|
96
|
+
encoding: "s16le"
|
|
97
|
+
}));
|
|
98
|
+
for (let e of i(n)) p(e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
pushFrame(e) {
|
|
103
|
+
l && p(e);
|
|
104
|
+
},
|
|
105
|
+
closeUtterance(e) {
|
|
106
|
+
l?.utteranceId === e && (p(JSON.stringify({
|
|
107
|
+
t: "end",
|
|
108
|
+
utteranceId: e
|
|
109
|
+
})), u = e, l = null);
|
|
110
|
+
},
|
|
111
|
+
cancelUtterance(e) {
|
|
112
|
+
if (l?.utteranceId === e) l = null;
|
|
113
|
+
else if (u !== e) return;
|
|
114
|
+
u = null, p(JSON.stringify({
|
|
115
|
+
t: "cancel",
|
|
116
|
+
utteranceId: e
|
|
117
|
+
}));
|
|
118
|
+
},
|
|
119
|
+
stop() {
|
|
120
|
+
l = null;
|
|
121
|
+
let e = a;
|
|
122
|
+
a = null, c = null;
|
|
123
|
+
try {
|
|
124
|
+
e?.close(1e3);
|
|
125
|
+
} catch {}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { n as CLOSE_CODE_ERRORS, e as MAX_SEND_BYTES, i as chunkFrames, a as createBackendStreamTransport };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { VoiceSessionPhase } from '../voiceSession';
|
|
2
|
+
export interface OnsetContext {
|
|
3
|
+
/** The viewer's "Hands-free interrupt (beta)" setting (or the test override). */
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
muted: boolean;
|
|
6
|
+
phase: VoiceSessionPhase;
|
|
7
|
+
/** Sound from the assistant is coming out of the speaker right now. */
|
|
8
|
+
playbackAudible: boolean;
|
|
9
|
+
}
|
|
10
|
+
export type OnsetPlan = {
|
|
11
|
+
kind: "ignore";
|
|
12
|
+
reason: "disabled" | "muted" | "not-in-session";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "open-utterance";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "correction";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "barge-in";
|
|
19
|
+
};
|
|
20
|
+
export declare function planOnset(context: OnsetContext): OnsetPlan;
|
|
21
|
+
/**
|
|
22
|
+
* Epoch guard. Every utterance opened at an onset gets a new epoch; every
|
|
23
|
+
* transcript carries the epoch of the utterance it came from. A transcript
|
|
24
|
+
* whose epoch is not the current one is a transport artifact (a late result of
|
|
25
|
+
* an utterance that was superseded) and is discarded BY EPOCH, never by looking
|
|
26
|
+
* at its words.
|
|
27
|
+
*/
|
|
28
|
+
export interface EpochGuard {
|
|
29
|
+
current(): number;
|
|
30
|
+
/** A new utterance opened: everything older is stale from now on. */
|
|
31
|
+
open(): number;
|
|
32
|
+
accepts(epoch: number): boolean;
|
|
33
|
+
/** Session ended or muted: nothing in flight may be delivered. */
|
|
34
|
+
invalidate(): void;
|
|
35
|
+
}
|
|
36
|
+
export declare function createEpochGuard(): EpochGuard;
|
|
37
|
+
/** Nothing heard this long after a barge-in: show "say that again, or tap to replay". */
|
|
38
|
+
export declare const NOTHING_HEARD_MS = 1500;
|
|
39
|
+
/** Phantom barge-ins (nothing heard afterwards) in one session before falling back to tap-first. */
|
|
40
|
+
export declare const PHANTOM_BARGE_INS_BEFORE_FALLBACK = 3;
|
|
41
|
+
/** Whether a barge-in that produced no words counts against the session. */
|
|
42
|
+
export declare function shouldFallBackToTap(phantomBargeIns: number): boolean;
|
|
43
|
+
//# sourceMappingURL=bargeIn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bargeIn.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/voice/handsfree/bargeIn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,iBAAiB,CAAC;IACzB,uEAAuE;IACvE,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,OAAO,GAAG,gBAAgB,CAAA;CAAE,GACnE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,GAC1B;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAU1D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,MAAM,CAAC;IAClB,qEAAqE;IACrE,IAAI,IAAI,MAAM,CAAC;IACf,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,kEAAkE;IAClE,UAAU,IAAI,IAAI,CAAC;CACpB;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAgB7C;AAED,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,OAAO,CAAC;AACrC,oGAAoG;AACpG,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAEpE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/handsfree/bargeIn.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return e.enabled ? e.muted ? {
|
|
4
|
+
kind: "ignore",
|
|
5
|
+
reason: "muted"
|
|
6
|
+
} : e.phase === "idle" || e.phase === "ended" ? {
|
|
7
|
+
kind: "ignore",
|
|
8
|
+
reason: "not-in-session"
|
|
9
|
+
} : e.phase === "speaking" || e.playbackAudible ? { kind: "barge-in" } : e.phase === "thinking" ? { kind: "correction" } : { kind: "open-utterance" } : {
|
|
10
|
+
kind: "ignore",
|
|
11
|
+
reason: "disabled"
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function t() {
|
|
15
|
+
let e = 0, t = !0;
|
|
16
|
+
return {
|
|
17
|
+
current: () => e,
|
|
18
|
+
open() {
|
|
19
|
+
return e += 1, t = !0, e;
|
|
20
|
+
},
|
|
21
|
+
accepts: (n) => t && n === e,
|
|
22
|
+
invalidate() {
|
|
23
|
+
e += 1, t = !1;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
var n = 1500, r = 3;
|
|
28
|
+
function i(e) {
|
|
29
|
+
return e >= 3;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { n as NOTHING_HEARD_MS, r as PHANTOM_BARGE_INS_BEFORE_FALLBACK, t as createEpochGuard, e as planOnset, i as shouldFallBackToTap };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AudioWorklet that turns the microphone into 16 kHz PCM (BOFF-8613, H3).
|
|
3
|
+
*
|
|
4
|
+
* A worklet module is loaded from a URL, and this package cannot assume a
|
|
5
|
+
* bundler that emits a second entry file for it, so the processor is a SOURCE
|
|
6
|
+
* STRING that `micCapture` loads through a Blob URL. It has no imports and no
|
|
7
|
+
* access to this file's scope; everything it needs is inside the string.
|
|
8
|
+
*
|
|
9
|
+
* What it does, per render quantum (128 samples at the device rate):
|
|
10
|
+
* - input 0 is the microphone, input 1 is the PLAYBACK REFERENCE bus (every
|
|
11
|
+
* sound the assistant makes is also sent there). Both are measured over the
|
|
12
|
+
* SAME samples, so the mic envelope and the reference envelope sit on one
|
|
13
|
+
* clock and only a fixed room lag separates them;
|
|
14
|
+
* - the microphone is decimated to 16 kHz with a windowed-sinc low-pass
|
|
15
|
+
* (cutoff 7.2 kHz). Forcing `sampleRate: 16000` on the context is not
|
|
16
|
+
* reliable across browsers, so the context runs at the device rate;
|
|
17
|
+
* - 320-sample (20 ms) frames are posted as s16le with their RMS (dBFS), zero-
|
|
18
|
+
* crossing rate and the reference RMS.
|
|
19
|
+
*
|
|
20
|
+
* The input buffer is preallocated; nothing is allocated per quantum.
|
|
21
|
+
*
|
|
22
|
+
* ★ The string is the SHIPPED code. The unit tests evaluate it with fake
|
|
23
|
+
* `AudioWorkletProcessor` / `registerProcessor` / `sampleRate` globals
|
|
24
|
+
* (`runCaptureWorkletForTest`) instead of testing a TypeScript copy of the
|
|
25
|
+
* maths, because a copy can be right while the string is wrong.
|
|
26
|
+
*/
|
|
27
|
+
/** Samples per frame at 16 kHz: 20 ms. */
|
|
28
|
+
export declare const CAPTURE_FRAME_SAMPLES = 320;
|
|
29
|
+
export declare const CAPTURE_SAMPLE_RATE = 16000;
|
|
30
|
+
export declare const CAPTURE_FRAME_MS = 20;
|
|
31
|
+
export declare const CAPTURE_PROCESSOR_NAME = "boff-handsfree-capture";
|
|
32
|
+
/** What the worklet posts for each frame. */
|
|
33
|
+
export interface CaptureFrameMessage {
|
|
34
|
+
/** s16le mono 16 kHz, `CAPTURE_FRAME_SAMPLES` samples. */
|
|
35
|
+
pcm: ArrayBuffer;
|
|
36
|
+
/** Microphone level over the frame, dBFS. */
|
|
37
|
+
rmsDb: number;
|
|
38
|
+
/** Reference-bus level over the same samples, dBFS (about -90 when silent). */
|
|
39
|
+
refDb: number;
|
|
40
|
+
/** Zero crossings per sample. */
|
|
41
|
+
zcr: number;
|
|
42
|
+
/** AudioContext time at the end of the frame, seconds. */
|
|
43
|
+
ctxTime: number;
|
|
44
|
+
seq: number;
|
|
45
|
+
}
|
|
46
|
+
export declare const CAPTURE_WORKLET_SOURCE = "\n\"use strict\";\nconst FRAME = 320;\nclass BoffHandsfreeCapture extends AudioWorkletProcessor {\n constructor() {\n super();\n this.step = sampleRate / 16000;\n this.half = 6 * this.step;\n this.fc = 7200 / sampleRate;\n this.buf = new Float32Array(Math.ceil(this.half * 2) + 4096);\n this.len = 0;\n this.pos = 0;\n this.frame = new Float32Array(FRAME);\n this.n = 0;\n this.seq = 0;\n this.refEnergy = 0;\n this.refCount = 0;\n this.stopped = false;\n this.port.onmessage = (e) => { if (e.data && e.data.type === \"stop\") this.stopped = true; };\n }\n tap(d) {\n const x = 2 * this.fc * d;\n const s = x === 0 ? 1 : Math.sin(Math.PI * x) / (Math.PI * x);\n return 2 * this.fc * s * 0.5 * (1 + Math.cos(Math.PI * d / this.half));\n }\n emit() {\n const pcm = new Int16Array(FRAME);\n let energy = 0;\n let crossings = 0;\n for (let i = 0; i < FRAME; i++) {\n const v = Math.max(-1, Math.min(1, this.frame[i]));\n pcm[i] = Math.round(v * 32767);\n energy += v * v;\n if (i > 0 && (v >= 0) !== (this.frame[i - 1] >= 0)) crossings++;\n }\n const refMean = this.refCount > 0 ? this.refEnergy / this.refCount : 0;\n this.port.postMessage({\n pcm: pcm.buffer,\n rmsDb: 10 * Math.log10(energy / FRAME + 1e-9),\n refDb: 10 * Math.log10(refMean + 1e-9),\n zcr: crossings / FRAME,\n ctxTime: currentTime,\n seq: this.seq++,\n }, [pcm.buffer]);\n this.n = 0;\n this.refEnergy = 0;\n this.refCount = 0;\n }\n process(inputs) {\n if (this.stopped) return false;\n const mic = inputs[0] && inputs[0][0];\n if (!mic) return true;\n const ref = inputs[1] && inputs[1][0];\n if (ref) { for (let i = 0; i < ref.length; i++) this.refEnergy += ref[i] * ref[i]; }\n this.refCount += mic.length;\n if (this.len + mic.length > this.buf.length) {\n const grown = new Float32Array((this.len + mic.length) * 2);\n grown.set(this.buf.subarray(0, this.len));\n this.buf = grown;\n }\n this.buf.set(mic, this.len);\n this.len += mic.length;\n while (this.pos + this.half < this.len) {\n const from = Math.max(0, Math.ceil(this.pos - this.half));\n const to = Math.floor(this.pos + this.half);\n let acc = 0;\n for (let i = from; i <= to; i++) acc += this.buf[i] * this.tap(i - this.pos);\n this.frame[this.n++] = acc;\n this.pos += this.step;\n if (this.n === FRAME) this.emit();\n }\n const drop = Math.max(0, Math.floor(this.pos - this.half));\n if (drop > 0) {\n this.buf.copyWithin(0, drop, this.len);\n this.len -= drop;\n this.pos -= drop;\n }\n return true;\n }\n}\nregisterProcessor(\"boff-handsfree-capture\", BoffHandsfreeCapture);\n";
|
|
47
|
+
//# sourceMappingURL=captureWorklet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureWorklet.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/voice/handsfree/captureWorklet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,0CAA0C;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,mBAAmB,QAAQ,CAAC;AACzC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAE/D,6CAA6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,GAAG,EAAE,WAAW,CAAC;IACjB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,sBAAsB,0tFAiFlC,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region src/shared/assistant/voice/handsfree/captureWorklet.ts
|
|
2
|
+
var e = 320, t = 16e3, n = 20, r = "boff-handsfree-capture", i = `
|
|
3
|
+
"use strict";
|
|
4
|
+
const FRAME = 320;
|
|
5
|
+
class BoffHandsfreeCapture extends AudioWorkletProcessor {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
this.step = sampleRate / ${t};
|
|
9
|
+
this.half = 6 * this.step;
|
|
10
|
+
this.fc = 7200 / sampleRate;
|
|
11
|
+
this.buf = new Float32Array(Math.ceil(this.half * 2) + 4096);
|
|
12
|
+
this.len = 0;
|
|
13
|
+
this.pos = 0;
|
|
14
|
+
this.frame = new Float32Array(FRAME);
|
|
15
|
+
this.n = 0;
|
|
16
|
+
this.seq = 0;
|
|
17
|
+
this.refEnergy = 0;
|
|
18
|
+
this.refCount = 0;
|
|
19
|
+
this.stopped = false;
|
|
20
|
+
this.port.onmessage = (e) => { if (e.data && e.data.type === "stop") this.stopped = true; };
|
|
21
|
+
}
|
|
22
|
+
tap(d) {
|
|
23
|
+
const x = 2 * this.fc * d;
|
|
24
|
+
const s = x === 0 ? 1 : Math.sin(Math.PI * x) / (Math.PI * x);
|
|
25
|
+
return 2 * this.fc * s * 0.5 * (1 + Math.cos(Math.PI * d / this.half));
|
|
26
|
+
}
|
|
27
|
+
emit() {
|
|
28
|
+
const pcm = new Int16Array(FRAME);
|
|
29
|
+
let energy = 0;
|
|
30
|
+
let crossings = 0;
|
|
31
|
+
for (let i = 0; i < FRAME; i++) {
|
|
32
|
+
const v = Math.max(-1, Math.min(1, this.frame[i]));
|
|
33
|
+
pcm[i] = Math.round(v * 32767);
|
|
34
|
+
energy += v * v;
|
|
35
|
+
if (i > 0 && (v >= 0) !== (this.frame[i - 1] >= 0)) crossings++;
|
|
36
|
+
}
|
|
37
|
+
const refMean = this.refCount > 0 ? this.refEnergy / this.refCount : 0;
|
|
38
|
+
this.port.postMessage({
|
|
39
|
+
pcm: pcm.buffer,
|
|
40
|
+
rmsDb: 10 * Math.log10(energy / FRAME + 1e-9),
|
|
41
|
+
refDb: 10 * Math.log10(refMean + 1e-9),
|
|
42
|
+
zcr: crossings / FRAME,
|
|
43
|
+
ctxTime: currentTime,
|
|
44
|
+
seq: this.seq++,
|
|
45
|
+
}, [pcm.buffer]);
|
|
46
|
+
this.n = 0;
|
|
47
|
+
this.refEnergy = 0;
|
|
48
|
+
this.refCount = 0;
|
|
49
|
+
}
|
|
50
|
+
process(inputs) {
|
|
51
|
+
if (this.stopped) return false;
|
|
52
|
+
const mic = inputs[0] && inputs[0][0];
|
|
53
|
+
if (!mic) return true;
|
|
54
|
+
const ref = inputs[1] && inputs[1][0];
|
|
55
|
+
if (ref) { for (let i = 0; i < ref.length; i++) this.refEnergy += ref[i] * ref[i]; }
|
|
56
|
+
this.refCount += mic.length;
|
|
57
|
+
if (this.len + mic.length > this.buf.length) {
|
|
58
|
+
const grown = new Float32Array((this.len + mic.length) * 2);
|
|
59
|
+
grown.set(this.buf.subarray(0, this.len));
|
|
60
|
+
this.buf = grown;
|
|
61
|
+
}
|
|
62
|
+
this.buf.set(mic, this.len);
|
|
63
|
+
this.len += mic.length;
|
|
64
|
+
while (this.pos + this.half < this.len) {
|
|
65
|
+
const from = Math.max(0, Math.ceil(this.pos - this.half));
|
|
66
|
+
const to = Math.floor(this.pos + this.half);
|
|
67
|
+
let acc = 0;
|
|
68
|
+
for (let i = from; i <= to; i++) acc += this.buf[i] * this.tap(i - this.pos);
|
|
69
|
+
this.frame[this.n++] = acc;
|
|
70
|
+
this.pos += this.step;
|
|
71
|
+
if (this.n === FRAME) this.emit();
|
|
72
|
+
}
|
|
73
|
+
const drop = Math.max(0, Math.floor(this.pos - this.half));
|
|
74
|
+
if (drop > 0) {
|
|
75
|
+
this.buf.copyWithin(0, drop, this.len);
|
|
76
|
+
this.len -= drop;
|
|
77
|
+
this.pos -= drop;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
registerProcessor("${r}", BoffHandsfreeCapture);
|
|
83
|
+
`;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { n as CAPTURE_FRAME_MS, e as CAPTURE_FRAME_SAMPLES, r as CAPTURE_PROCESSOR_NAME, t as CAPTURE_SAMPLE_RATE, i as CAPTURE_WORKLET_SOURCE };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The playback-reference echo gate (BOFF-8613, H3).
|
|
3
|
+
*
|
|
4
|
+
* Browser echo cancellation is best effort: it varies by handset, output route
|
|
5
|
+
* and volume. So the app adds its own ACOUSTIC evidence, and never decides by
|
|
6
|
+
* text: whether a stretch of audio is the user is settled BEFORE any transcript
|
|
7
|
+
* exists. (Dropping words because they resemble the assistant's sentence is
|
|
8
|
+
* what made "keep 50, not 15" vanish; a genuine correction that repeats the
|
|
9
|
+
* assistant's words or quantities must be kept.)
|
|
10
|
+
*
|
|
11
|
+
* ★ WHY COHERENCE AND NOT A LEVEL RATIO. The first version of this gate compared
|
|
12
|
+
* the microphone LEVEL with the assistant's level plus a calibrated offset. The
|
|
13
|
+
* headless-browser test showed why that cannot work with the processing the
|
|
14
|
+
* capture asks for: automatic gain control pumps a quiet echo up to the same
|
|
15
|
+
* loudness as a person (a -44 dBFS echo came back at -18 dBFS, the level of a
|
|
16
|
+
* real talker), so "louder than the expected echo" fired on the assistant's own
|
|
17
|
+
* voice. The ratio is not stable; the SHAPE is. An echo is a delayed copy of the
|
|
18
|
+
* reference: its loudness envelope rises and falls with the assistant's, one
|
|
19
|
+
* fixed room lag later, whatever gain the processing applied. A person talking
|
|
20
|
+
* over the assistant does not follow it.
|
|
21
|
+
*
|
|
22
|
+
* Per playback:
|
|
23
|
+
* 1. CALIBRATE the lag (0-500 ms) over the first second: the lag at which the
|
|
24
|
+
* mic envelope is most correlated with the reference envelope. Kept as a
|
|
25
|
+
* PRIOR, so the next reply is gated from its first frame. With no prior yet,
|
|
26
|
+
* onsets inside the first second of the very first reply cannot be
|
|
27
|
+
* attributed and are refused (fail closed: a false barge-in is the most
|
|
28
|
+
* visible failure, tap-to-interrupt is the fallback).
|
|
29
|
+
* 2. A frame is UNEXPLAINED by the reference when the correlation of the last
|
|
30
|
+
* `windowFrames` mic levels with the lagged reference levels is below
|
|
31
|
+
* `lowCorr`. The gate PASSES a frame after `lowRunFrames` consecutive
|
|
32
|
+
* unexplained frames (a single dip is not enough: echo-only audio dips to
|
|
33
|
+
* about 0.5 now and then). A reference that is silent over the window passes:
|
|
34
|
+
* there is no echo to confuse the user with. A reference that is audible but
|
|
35
|
+
* flat gives no shape to compare: refused.
|
|
36
|
+
* 3. With NO reference (the device `speechSynthesis` voice does not pass
|
|
37
|
+
* through the browser's output graph) nothing can be attributed: refused
|
|
38
|
+
* while it speaks. Tap-to-interrupt is the fallback there, and it is said so.
|
|
39
|
+
*
|
|
40
|
+
* Known limit, measured in the browser test and the design spike: this is
|
|
41
|
+
* envelope evidence, not an adaptive filter. A person who speaks in lock-step
|
|
42
|
+
* with the assistant's loudness pattern for a second is indistinguishable from
|
|
43
|
+
* an echo; the device kit is where handsets are measured.
|
|
44
|
+
*/
|
|
45
|
+
export interface EchoGateParams {
|
|
46
|
+
/** Frames the coherence is measured over (12 x 20 ms = 240 ms). */
|
|
47
|
+
windowFrames: number;
|
|
48
|
+
/** Correlation below this is "not following the reference". */
|
|
49
|
+
lowCorr: number;
|
|
50
|
+
/** Consecutive unexplained frames before a frame passes. */
|
|
51
|
+
lowRunFrames: number;
|
|
52
|
+
/** The same, with a headset hint (the echo path is usually absent). */
|
|
53
|
+
headsetLowRunFrames: number;
|
|
54
|
+
/** Frames of evidence before the lag can be estimated (50 x 20 ms = 1 s). */
|
|
55
|
+
calibFrames: number;
|
|
56
|
+
/** Longest lag searched, in frames (25 x 20 ms = 500 ms). */
|
|
57
|
+
maxLagFrames: number;
|
|
58
|
+
/** The best lag must correlate at least this well to be believed. */
|
|
59
|
+
minCalibCorr: number;
|
|
60
|
+
/** Frames the gate stays armed after playback stops (the room tail and the lag). */
|
|
61
|
+
tailFrames: number;
|
|
62
|
+
/** Levels are clamped to [floorDb, 0] before comparing envelopes. */
|
|
63
|
+
floorDb: number;
|
|
64
|
+
/** A reference at or below this over the window is silence. */
|
|
65
|
+
referenceSilentDb: number;
|
|
66
|
+
/** Mic p90 at or below this while the reference plays means no echo path. */
|
|
67
|
+
quietMicDb: number;
|
|
68
|
+
/** Envelope standard deviation (dB) below which a signal is "flat". */
|
|
69
|
+
flatStdDb: number;
|
|
70
|
+
/**
|
|
71
|
+
* The room rings after the assistant stops, and gain control lifts that tail, so the
|
|
72
|
+
* echo of a word outlasts the word: the reference is compared with a copy that falls
|
|
73
|
+
* by at most this many dB per frame (a tail model), not with the bare envelope.
|
|
74
|
+
*/
|
|
75
|
+
tailDecayDbPerFrame: number;
|
|
76
|
+
}
|
|
77
|
+
export declare const DEFAULT_ECHO_GATE_PARAMS: Readonly<EchoGateParams>;
|
|
78
|
+
export type GateReason = "no-playback" | "calibrating"
|
|
79
|
+
/** The assistant is on the device voice: no reference exists, so nothing can be attributed. */
|
|
80
|
+
| "no-reference" | "reference-silent"
|
|
81
|
+
/** No echo reaches the microphone (headphones, or a very good canceller). */
|
|
82
|
+
| "no-echo-path"
|
|
83
|
+
/** The microphone follows the reference: this is the assistant, not the user. */
|
|
84
|
+
| "follows-reference"
|
|
85
|
+
/** Audible reference without enough shape to compare. */
|
|
86
|
+
| "flat-reference"
|
|
87
|
+
/** The microphone does not follow the reference: this may be the user. */
|
|
88
|
+
| "uncorrelated";
|
|
89
|
+
export interface GateVerdict {
|
|
90
|
+
pass: boolean;
|
|
91
|
+
reason: GateReason;
|
|
92
|
+
/** Correlation of the last window with the lagged reference, when measured. */
|
|
93
|
+
coherence: number | null;
|
|
94
|
+
}
|
|
95
|
+
export declare class EchoGate {
|
|
96
|
+
private readonly params;
|
|
97
|
+
/** Clamped dBFS history, absolute index = `base` + position. */
|
|
98
|
+
private mic;
|
|
99
|
+
private ref;
|
|
100
|
+
/** `ref` with the room tail: falls by at most `tailDecayDbPerFrame` per frame. */
|
|
101
|
+
private tail;
|
|
102
|
+
private base;
|
|
103
|
+
private total;
|
|
104
|
+
private playing;
|
|
105
|
+
private hasReference;
|
|
106
|
+
private playStart;
|
|
107
|
+
private armedUntil;
|
|
108
|
+
private current;
|
|
109
|
+
private prior;
|
|
110
|
+
/** The reference plays but no echo reaches the microphone (this playback only). */
|
|
111
|
+
private noEchoPath;
|
|
112
|
+
private lowRun;
|
|
113
|
+
private headset;
|
|
114
|
+
constructor(params?: Partial<EchoGateParams>);
|
|
115
|
+
get isPlaying(): boolean;
|
|
116
|
+
/** True once a lag is known, i.e. the next reply is gated from its first frame. */
|
|
117
|
+
get hasPrior(): boolean;
|
|
118
|
+
/** What the last calibration found, for telemetry and the device kit. */
|
|
119
|
+
get calibration(): {
|
|
120
|
+
lagMs: number;
|
|
121
|
+
corr: number;
|
|
122
|
+
} | null;
|
|
123
|
+
setHeadset(headset: boolean): void;
|
|
124
|
+
/** The assistant started making sound. `withReference`: it passes through the reference bus. */
|
|
125
|
+
playbackStarted(withReference: boolean): void;
|
|
126
|
+
playbackStopped(): void;
|
|
127
|
+
/** Feed one frame; returns whether THIS frame may be the user's. */
|
|
128
|
+
pushFrame(micDb: number, refDb: number): GateVerdict;
|
|
129
|
+
private at;
|
|
130
|
+
/** The loudest of `array` over [absolute - span, absolute], ignoring what history no longer holds. */
|
|
131
|
+
private windowMax;
|
|
132
|
+
/** Correlation of mic[idx-w+1..idx] with the reference one lag earlier; null when history is short. */
|
|
133
|
+
private coherence;
|
|
134
|
+
/** Estimate the lag from the frames since playback began; sets `current` and `prior` when believable. */
|
|
135
|
+
private calibrate;
|
|
136
|
+
private trim;
|
|
137
|
+
/** Forget everything, including the prior (a new session, or the output route changed). */
|
|
138
|
+
reset(): void;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=echoGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"echoGate.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/voice/handsfree/echoGate.ts"],"names":[],"mappings":"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"}
|