@reins-ai/voice 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/barge-in.d.ts +42 -0
- package/dist/barge-in.d.ts.map +1 -0
- package/dist/barge-in.js +78 -0
- package/dist/barge-in.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/config.js.map +1 -0
- package/dist/download.d.ts +18 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +37 -0
- package/dist/download.js.map +1 -0
- package/dist/ear.d.ts +158 -0
- package/dist/ear.d.ts.map +1 -0
- package/dist/ear.js +387 -0
- package/dist/ear.js.map +1 -0
- package/dist/log.d.ts +6 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +13 -0
- package/dist/log.js.map +1 -0
- package/dist/mic.d.ts +19 -0
- package/dist/mic.d.ts.map +1 -0
- package/dist/mic.js +43 -0
- package/dist/mic.js.map +1 -0
- package/dist/piper.d.ts +38 -0
- package/dist/piper.d.ts.map +1 -0
- package/dist/piper.js +90 -0
- package/dist/piper.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +92 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +15 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +73 -0
- package/dist/setup.js.map +1 -0
- package/dist/speak.d.ts +74 -0
- package/dist/speak.d.ts.map +1 -0
- package/dist/speak.js +188 -0
- package/dist/speak.js.map +1 -0
- package/dist/speech-check.d.ts +27 -0
- package/dist/speech-check.d.ts.map +1 -0
- package/dist/speech-check.js +51 -0
- package/dist/speech-check.js.map +1 -0
- package/dist/stdout-guard.d.ts +2 -0
- package/dist/stdout-guard.d.ts.map +1 -0
- package/dist/stdout-guard.js +8 -0
- package/dist/stdout-guard.js.map +1 -0
- package/dist/stt.d.ts +31 -0
- package/dist/stt.d.ts.map +1 -0
- package/dist/stt.js +86 -0
- package/dist/stt.js.map +1 -0
- package/dist/turn.d.ts +28 -0
- package/dist/turn.d.ts.map +1 -0
- package/dist/turn.js +60 -0
- package/dist/turn.js.map +1 -0
- package/dist/vad.d.ts +53 -0
- package/dist/vad.d.ts.map +1 -0
- package/dist/vad.js +120 -0
- package/dist/vad.js.map +1 -0
- package/package.json +31 -4
package/dist/ear.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { type Log } from './log.js';
|
|
2
|
+
import type { OpenMic } from './mic.js';
|
|
3
|
+
import type { SpeechCheck } from './speech-check.js';
|
|
4
|
+
import type { Transcriber } from './stt.js';
|
|
5
|
+
import { type TurnCheck } from './turn.js';
|
|
6
|
+
import { type VadOptions } from './vad.js';
|
|
7
|
+
export declare const MAX_WAIT_MS = 60000;
|
|
8
|
+
/**
|
|
9
|
+
* What the AI said may come back through the microphone (echo) up to this long after it finished saying it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ECHO_WINDOW_MS = 10000;
|
|
12
|
+
/** A sentence is echo if at least this share of its words came from what the AI said… */
|
|
13
|
+
export declare const ECHO_OVERLAP = 0.7;
|
|
14
|
+
/** …and it has at least this many words (a user's "yes" or "ok" is never echo). */
|
|
15
|
+
export declare const ECHO_MIN_WORDS = 3;
|
|
16
|
+
/**
|
|
17
|
+
* If the AI does not call listen again within this time, the conversation is over and the microphone closes.
|
|
18
|
+
*/
|
|
19
|
+
export declare const IDLE_CLOSE_MS = 30000;
|
|
20
|
+
/** A "sentence" with less voice energy than this is a knock or a noise and is not transcribed. */
|
|
21
|
+
export declare const MIN_VOICED_MS = 300;
|
|
22
|
+
/**
|
|
23
|
+
* With something already understood, listen waits at most this long for the speaker to stop (it may be a TV).
|
|
24
|
+
*/
|
|
25
|
+
export declare const SETTLE_MS = 3000;
|
|
26
|
+
/**
|
|
27
|
+
* At every 0.3 s pause (10 blocks) Smart Turn is asked whether the user has finished. If not, the 1.5 s limit
|
|
28
|
+
* applies.
|
|
29
|
+
*/
|
|
30
|
+
export declare const PAUSE_FRAMES = 10;
|
|
31
|
+
/**
|
|
32
|
+
* Smart Turn only closes a sentence early when it has at least this much voice, so it does not split a short
|
|
33
|
+
* question in pieces.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MIN_TURN_VOICED_MS = 1000;
|
|
36
|
+
/**
|
|
37
|
+
* A sentence that started while the AI was talking and is not its echo only cuts the AI off with at least
|
|
38
|
+
* this much voice. Echo leftovers are chunks with little voice (330 ms in 3 s) that Whisper sometimes
|
|
39
|
+
* transcribes as other words; a user saying "stop, stop" had 750 ms.
|
|
40
|
+
*/
|
|
41
|
+
export declare const MIN_INTERRUPT_VOICED_MS = 500;
|
|
42
|
+
/**
|
|
43
|
+
* …and, under 3 words, it must be almost all voice. An echo leftover is a few words in a long chunk with
|
|
44
|
+
* little voice (510 ms of voice in 3.4 s, 15 %); a single "stop" from the user is almost all voice.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MIN_INTERRUPT_VOICE_RATIO = 0.3;
|
|
47
|
+
/**
|
|
48
|
+
* What listen returns when the user cut the AI off, so the AI knows the user did not hear everything it said.
|
|
49
|
+
*/
|
|
50
|
+
export declare const INTERRUPTED = "(the user interrupted you while you were speaking)";
|
|
51
|
+
export interface EarOptions {
|
|
52
|
+
openMic: OpenMic;
|
|
53
|
+
transcriber: Transcriber;
|
|
54
|
+
/**
|
|
55
|
+
* Checks that a sentence has human voice before Whisper: over noise, Whisper makes up sentences (from the
|
|
56
|
+
* subtitles it was trained on). Works for any language.
|
|
57
|
+
*/
|
|
58
|
+
speechCheck?: SpeechCheck;
|
|
59
|
+
vad?: VadOptions;
|
|
60
|
+
/**
|
|
61
|
+
* Decides whether a pause ends the turn (Smart Turn). With it, the sentence closes after 0.3 s of silence
|
|
62
|
+
* if the user has finished; without it, always after 1.5 s.
|
|
63
|
+
*/
|
|
64
|
+
turnCheck?: TurnCheck;
|
|
65
|
+
/** A user sentence just closed while the AI listens, to let the user know they were heard. */
|
|
66
|
+
onPhrase?: () => void;
|
|
67
|
+
/** The user started talking while the AI talks: it must go quiet. */
|
|
68
|
+
onBargeIn?: () => void;
|
|
69
|
+
log?: Log;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Continuous listening. The microphone stays open from the first listen, also while the AI talks, so the user
|
|
73
|
+
* can answer or interrupt at once. Each long pause closes a sentence, which is transcribed and stored; listen
|
|
74
|
+
* returns it whole. Whatever matches what the AI just said is its own echo from the speakers and is
|
|
75
|
+
* discarded.
|
|
76
|
+
*/
|
|
77
|
+
export declare class Ear {
|
|
78
|
+
private readonly options;
|
|
79
|
+
private mic;
|
|
80
|
+
private vad;
|
|
81
|
+
private leftover;
|
|
82
|
+
/**
|
|
83
|
+
* Understood text not collected by listen yet. `fromUser`: it started while the AI was quiet, so it is not
|
|
84
|
+
* an echo leftover.
|
|
85
|
+
*/
|
|
86
|
+
private heard;
|
|
87
|
+
private failure;
|
|
88
|
+
private waiters;
|
|
89
|
+
private transcribing;
|
|
90
|
+
private pending;
|
|
91
|
+
private speaking;
|
|
92
|
+
private said;
|
|
93
|
+
/** Bumped on close, so transcriptions in progress are discarded. */
|
|
94
|
+
private generation;
|
|
95
|
+
private idleTimer;
|
|
96
|
+
private heardAt;
|
|
97
|
+
/** Bumped on every interrupt, so the waiting listen returns. */
|
|
98
|
+
private interrupts;
|
|
99
|
+
/** Bumped on every closed sentence, so a Smart Turn answer for a previous sentence is ignored. */
|
|
100
|
+
private utteranceId;
|
|
101
|
+
/** Smart Turn is deciding: it is not asked again, since each pass delays Silero and Whisper. */
|
|
102
|
+
private checkingTurn;
|
|
103
|
+
private gate;
|
|
104
|
+
/** The current sentence started while the AI was talking, so it is almost always its echo. */
|
|
105
|
+
private startedWithAI;
|
|
106
|
+
/** The user cut the AI off; the AI is told on the next listen. */
|
|
107
|
+
private interruptedAI;
|
|
108
|
+
constructor(options: EarOptions);
|
|
109
|
+
/**
|
|
110
|
+
* Everything the user said since the last call, whole: waits for them to stop talking and for the rest to
|
|
111
|
+
* be transcribed. It never gets stuck with endless background voice (a TV, a video): with something already
|
|
112
|
+
* understood, it returns at most SETTLE_MS later, and after maxWaitMs it returns whatever there is (or
|
|
113
|
+
* nothing). Anything still playing is not lost: it goes to the next listen.
|
|
114
|
+
*/
|
|
115
|
+
next(maxWaitMs?: number): Promise<string>;
|
|
116
|
+
/**
|
|
117
|
+
* A 30 ms chunk about to play from the speaker (its level and when it plays). `first` starts a sentence.
|
|
118
|
+
*/
|
|
119
|
+
playing(level: number, at: number, first?: boolean): void;
|
|
120
|
+
/**
|
|
121
|
+
* The AI is about to say this text. Returns the function to call when it finishes: it closes the current
|
|
122
|
+
* sentence, which is its echo from the speakers, so it does not merge with what the user says next (if they
|
|
123
|
+
* merged, the echo filter would also discard the user's words).
|
|
124
|
+
*/
|
|
125
|
+
saying(text: string): () => void;
|
|
126
|
+
/**
|
|
127
|
+
* Another tool arrives (speak, stop or another listen): the waiting listen returns now, without consuming
|
|
128
|
+
* what was heard. This way the AI can speak even if an earlier listen is still waiting.
|
|
129
|
+
*/
|
|
130
|
+
interrupt(): void;
|
|
131
|
+
/** Ends the conversation: closes the microphone and clears everything pending. */
|
|
132
|
+
stop(): void;
|
|
133
|
+
private open;
|
|
134
|
+
private onAudio;
|
|
135
|
+
/**
|
|
136
|
+
* The user talks over the AI: it goes quiet, and the user's sentence starts here (what came before was the
|
|
137
|
+
* AI's voice, which the echo filter would discard along with the user's words).
|
|
138
|
+
*/
|
|
139
|
+
private bargeIn;
|
|
140
|
+
/**
|
|
141
|
+
* 0.3 s pause: Smart Turn tells whether the user has finished. If so and they are still quiet, the sentence
|
|
142
|
+
* closes now instead of after 1.5 s. If they started talking again meanwhile, the answer is ignored.
|
|
143
|
+
*/
|
|
144
|
+
private checkTurn;
|
|
145
|
+
/** Closes the current sentence and prepares the next one; the detector keeps the room noise level. */
|
|
146
|
+
private endUtterance;
|
|
147
|
+
private log;
|
|
148
|
+
private aiSpeaking;
|
|
149
|
+
/** Sentences are transcribed in order, one after another. */
|
|
150
|
+
private transcribe;
|
|
151
|
+
/**
|
|
152
|
+
* With fewer than `minWords` words it is never echo (a user's "yes"); 1 for what started while the AI was
|
|
153
|
+
* talking.
|
|
154
|
+
*/
|
|
155
|
+
private isEcho;
|
|
156
|
+
private wake;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=ear.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ear.d.ts","sourceRoot":"","sources":["../src/ear.ts"],"names":[],"mappings":"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"}
|
package/dist/ear.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { silent } from './log.js';
|
|
2
|
+
import { EchoGate } from './barge-in.js';
|
|
3
|
+
import { TURN_COMPLETE } from './turn.js';
|
|
4
|
+
import { FRAME_SAMPLES, VoiceDetector } from './vad.js';
|
|
5
|
+
export const MAX_WAIT_MS = 60_000;
|
|
6
|
+
/**
|
|
7
|
+
* What the AI said may come back through the microphone (echo) up to this long after it finished saying it.
|
|
8
|
+
*/
|
|
9
|
+
export const ECHO_WINDOW_MS = 10_000;
|
|
10
|
+
/** A sentence is echo if at least this share of its words came from what the AI said… */
|
|
11
|
+
export const ECHO_OVERLAP = 0.7;
|
|
12
|
+
/** …and it has at least this many words (a user's "yes" or "ok" is never echo). */
|
|
13
|
+
export const ECHO_MIN_WORDS = 3;
|
|
14
|
+
/**
|
|
15
|
+
* If the AI does not call listen again within this time, the conversation is over and the microphone closes.
|
|
16
|
+
*/
|
|
17
|
+
export const IDLE_CLOSE_MS = 30_000;
|
|
18
|
+
/** A "sentence" with less voice energy than this is a knock or a noise and is not transcribed. */
|
|
19
|
+
export const MIN_VOICED_MS = 300;
|
|
20
|
+
/**
|
|
21
|
+
* With something already understood, listen waits at most this long for the speaker to stop (it may be a TV).
|
|
22
|
+
*/
|
|
23
|
+
export const SETTLE_MS = 3_000;
|
|
24
|
+
/**
|
|
25
|
+
* At every 0.3 s pause (10 blocks) Smart Turn is asked whether the user has finished. If not, the 1.5 s limit
|
|
26
|
+
* applies.
|
|
27
|
+
*/
|
|
28
|
+
export const PAUSE_FRAMES = 10;
|
|
29
|
+
/**
|
|
30
|
+
* Smart Turn only closes a sentence early when it has at least this much voice, so it does not split a short
|
|
31
|
+
* question in pieces.
|
|
32
|
+
*/
|
|
33
|
+
export const MIN_TURN_VOICED_MS = 1_000;
|
|
34
|
+
/**
|
|
35
|
+
* When the user cuts the AI off, their sentence starts this much earlier (17 blocks, 0.5 s); what came before
|
|
36
|
+
* was the AI.
|
|
37
|
+
*/
|
|
38
|
+
const BARGE_KEEP_FRAMES = 17;
|
|
39
|
+
/**
|
|
40
|
+
* A sentence that started while the AI was talking and is not its echo only cuts the AI off with at least
|
|
41
|
+
* this much voice. Echo leftovers are chunks with little voice (330 ms in 3 s) that Whisper sometimes
|
|
42
|
+
* transcribes as other words; a user saying "stop, stop" had 750 ms.
|
|
43
|
+
*/
|
|
44
|
+
export const MIN_INTERRUPT_VOICED_MS = 500;
|
|
45
|
+
/**
|
|
46
|
+
* …and, under 3 words, it must be almost all voice. An echo leftover is a few words in a long chunk with
|
|
47
|
+
* little voice (510 ms of voice in 3.4 s, 15 %); a single "stop" from the user is almost all voice.
|
|
48
|
+
*/
|
|
49
|
+
export const MIN_INTERRUPT_VOICE_RATIO = 0.3;
|
|
50
|
+
/**
|
|
51
|
+
* Whether it is a leftover of the AI's echo, even if Whisper transcribed it with words the AI did not say.
|
|
52
|
+
*/
|
|
53
|
+
function echoLeftover(text, voicedMs, audioMs) {
|
|
54
|
+
const words = (text.match(/\p{L}+/gu) ?? []).length;
|
|
55
|
+
return voicedMs < MIN_INTERRUPT_VOICED_MS || (words < 3 && voicedMs / audioMs < MIN_INTERRUPT_VOICE_RATIO);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* What listen returns when the user cut the AI off, so the AI knows the user did not hear everything it said.
|
|
59
|
+
*/
|
|
60
|
+
export const INTERRUPTED = '(the user interrupted you while you were speaking)';
|
|
61
|
+
const rms = (frame) => {
|
|
62
|
+
let sum = 0;
|
|
63
|
+
for (const sample of frame)
|
|
64
|
+
sum += sample * sample;
|
|
65
|
+
return Math.sqrt(sum / frame.length);
|
|
66
|
+
};
|
|
67
|
+
/** Whisper sometimes makes up an endlessly repeated word over noise or music ("yes, yes, yes…"). */
|
|
68
|
+
function isRepetition(text) {
|
|
69
|
+
const all = text.toLowerCase().match(/\p{L}+/gu) ?? [];
|
|
70
|
+
return all.length >= 8 && new Set(all).size / all.length < 0.25;
|
|
71
|
+
}
|
|
72
|
+
/** Words without accents or capitals, to compare what was heard with what was said. */
|
|
73
|
+
function words(text) {
|
|
74
|
+
const plain = text.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
75
|
+
return new Set(plain.match(/[a-z0-9ñ]+/g) ?? []);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Continuous listening. The microphone stays open from the first listen, also while the AI talks, so the user
|
|
79
|
+
* can answer or interrupt at once. Each long pause closes a sentence, which is transcribed and stored; listen
|
|
80
|
+
* returns it whole. Whatever matches what the AI just said is its own echo from the speakers and is
|
|
81
|
+
* discarded.
|
|
82
|
+
*/
|
|
83
|
+
export class Ear {
|
|
84
|
+
options;
|
|
85
|
+
mic;
|
|
86
|
+
vad;
|
|
87
|
+
leftover = Buffer.alloc(0);
|
|
88
|
+
/**
|
|
89
|
+
* Understood text not collected by listen yet. `fromUser`: it started while the AI was quiet, so it is not
|
|
90
|
+
* an echo leftover.
|
|
91
|
+
*/
|
|
92
|
+
heard = [];
|
|
93
|
+
failure;
|
|
94
|
+
waiters = [];
|
|
95
|
+
transcribing = Promise.resolve();
|
|
96
|
+
pending = 0;
|
|
97
|
+
speaking = false;
|
|
98
|
+
said = [];
|
|
99
|
+
/** Bumped on close, so transcriptions in progress are discarded. */
|
|
100
|
+
generation = 0;
|
|
101
|
+
idleTimer;
|
|
102
|
+
heardAt = 0;
|
|
103
|
+
/** Bumped on every interrupt, so the waiting listen returns. */
|
|
104
|
+
interrupts = 0;
|
|
105
|
+
/** Bumped on every closed sentence, so a Smart Turn answer for a previous sentence is ignored. */
|
|
106
|
+
utteranceId = 0;
|
|
107
|
+
/** Smart Turn is deciding: it is not asked again, since each pass delays Silero and Whisper. */
|
|
108
|
+
checkingTurn = false;
|
|
109
|
+
gate = new EchoGate();
|
|
110
|
+
/** The current sentence started while the AI was talking, so it is almost always its echo. */
|
|
111
|
+
startedWithAI = false;
|
|
112
|
+
/** The user cut the AI off; the AI is told on the next listen. */
|
|
113
|
+
interruptedAI = false;
|
|
114
|
+
constructor(options) {
|
|
115
|
+
this.options = options;
|
|
116
|
+
this.vad = new VoiceDetector(options.vad);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Everything the user said since the last call, whole: waits for them to stop talking and for the rest to
|
|
120
|
+
* be transcribed. It never gets stuck with endless background voice (a TV, a video): with something already
|
|
121
|
+
* understood, it returns at most SETTLE_MS later, and after maxWaitMs it returns whatever there is (or
|
|
122
|
+
* nothing). Anything still playing is not lost: it goes to the next listen.
|
|
123
|
+
*/
|
|
124
|
+
async next(maxWaitMs = MAX_WAIT_MS) {
|
|
125
|
+
clearTimeout(this.idleTimer);
|
|
126
|
+
this.open();
|
|
127
|
+
const interrupts = this.interrupts;
|
|
128
|
+
let timedOut = false;
|
|
129
|
+
const timer = setTimeout(() => {
|
|
130
|
+
timedOut = true;
|
|
131
|
+
this.wake();
|
|
132
|
+
}, maxWaitMs);
|
|
133
|
+
let settleTimer;
|
|
134
|
+
const busy = () => this.speaking || this.pending > 0;
|
|
135
|
+
while (!this.failure && !timedOut && interrupts === this.interrupts) {
|
|
136
|
+
if (this.heard.length) {
|
|
137
|
+
const left = this.heardAt + SETTLE_MS - Date.now();
|
|
138
|
+
if (!busy() || left <= 0)
|
|
139
|
+
break;
|
|
140
|
+
settleTimer ??= setTimeout(() => this.wake(), left);
|
|
141
|
+
}
|
|
142
|
+
await new Promise((resolve) => this.waiters.push(resolve));
|
|
143
|
+
}
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
clearTimeout(settleTimer);
|
|
146
|
+
// Another tool interrupted it: it returns without consuming what was heard, which is kept for the next
|
|
147
|
+
// listen.
|
|
148
|
+
if (interrupts !== this.interrupts)
|
|
149
|
+
return '';
|
|
150
|
+
// If the AI does not listen again soon, the conversation is over and the microphone does not stay open.
|
|
151
|
+
this.idleTimer = setTimeout(() => this.stop(), IDLE_CLOSE_MS);
|
|
152
|
+
if (this.failure) {
|
|
153
|
+
const reason = this.failure;
|
|
154
|
+
this.failure = undefined;
|
|
155
|
+
throw new Error(reason);
|
|
156
|
+
}
|
|
157
|
+
let text = this.heard.map((h) => h.text).join(' ');
|
|
158
|
+
this.heard = [];
|
|
159
|
+
// The AI is told it was cut off once, even if what cut it off never became a sentence.
|
|
160
|
+
if (text && this.interruptedAI)
|
|
161
|
+
text = `${INTERRUPTED} ${text}`;
|
|
162
|
+
this.interruptedAI = false;
|
|
163
|
+
return text;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A 30 ms chunk about to play from the speaker (its level and when it plays). `first` starts a sentence.
|
|
167
|
+
*/
|
|
168
|
+
playing(level, at, first = false) {
|
|
169
|
+
this.gate.play(level, at, first);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The AI is about to say this text. Returns the function to call when it finishes: it closes the current
|
|
173
|
+
* sentence, which is its echo from the speakers, so it does not merge with what the user says next (if they
|
|
174
|
+
* merged, the echo filter would also discard the user's words).
|
|
175
|
+
*/
|
|
176
|
+
saying(text) {
|
|
177
|
+
const said = { words: words(text), until: Infinity };
|
|
178
|
+
this.said.push(said);
|
|
179
|
+
return () => {
|
|
180
|
+
said.until = Math.max(Number.isFinite(said.until) ? said.until : 0, Date.now() + ECHO_WINDOW_MS);
|
|
181
|
+
if (!said.cut && this.vad.current === 'speaking')
|
|
182
|
+
this.endUtterance();
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Another tool arrives (speak, stop or another listen): the waiting listen returns now, without consuming
|
|
187
|
+
* what was heard. This way the AI can speak even if an earlier listen is still waiting.
|
|
188
|
+
*/
|
|
189
|
+
interrupt() {
|
|
190
|
+
this.interrupts++;
|
|
191
|
+
this.wake();
|
|
192
|
+
}
|
|
193
|
+
/** Ends the conversation: closes the microphone and clears everything pending. */
|
|
194
|
+
stop() {
|
|
195
|
+
clearTimeout(this.idleTimer);
|
|
196
|
+
this.mic?.stop();
|
|
197
|
+
this.mic = undefined;
|
|
198
|
+
this.generation++;
|
|
199
|
+
this.utteranceId++;
|
|
200
|
+
this.gate = new EchoGate();
|
|
201
|
+
this.interruptedAI = false;
|
|
202
|
+
this.heard = [];
|
|
203
|
+
this.said = [];
|
|
204
|
+
this.speaking = false;
|
|
205
|
+
this.leftover = Buffer.alloc(0);
|
|
206
|
+
this.vad = new VoiceDetector(this.options.vad);
|
|
207
|
+
this.wake();
|
|
208
|
+
}
|
|
209
|
+
open() {
|
|
210
|
+
if (this.mic)
|
|
211
|
+
return;
|
|
212
|
+
const mic = this.options.openMic();
|
|
213
|
+
this.mic = mic;
|
|
214
|
+
mic.onData((chunk) => this.onAudio(chunk));
|
|
215
|
+
// If the microphone closes by itself, the next listen opens it again.
|
|
216
|
+
mic.onExit((reason) => {
|
|
217
|
+
if (this.mic !== mic)
|
|
218
|
+
return;
|
|
219
|
+
this.mic = undefined;
|
|
220
|
+
this.failure = `Cannot use the microphone: ${reason}`;
|
|
221
|
+
this.wake();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
onAudio(chunk) {
|
|
225
|
+
if (!this.mic)
|
|
226
|
+
return;
|
|
227
|
+
const bytes = Buffer.concat([this.leftover, chunk]);
|
|
228
|
+
const frameBytes = FRAME_SAMPLES * 2;
|
|
229
|
+
let offset = 0;
|
|
230
|
+
for (; offset + frameBytes <= bytes.length; offset += frameBytes) {
|
|
231
|
+
const frame = new Float32Array(FRAME_SAMPLES);
|
|
232
|
+
for (let i = 0; i < FRAME_SAMPLES; i++)
|
|
233
|
+
frame[i] = bytes.readInt16LE(offset + i * 2) / 32_768;
|
|
234
|
+
if (this.aiSpeaking() && this.gate.mic(rms(frame), Date.now(), this.vad.threshold))
|
|
235
|
+
this.bargeIn();
|
|
236
|
+
const was = this.speaking;
|
|
237
|
+
// No noise is learned while the AI talks: after a minute of reading, its echo passed for noise and the user's
|
|
238
|
+
// "stop" no longer crossed the threshold.
|
|
239
|
+
const state = this.vad.push(frame, !this.aiSpeaking());
|
|
240
|
+
this.speaking = state === 'speaking';
|
|
241
|
+
if (this.speaking && !was)
|
|
242
|
+
this.startedWithAI = this.aiSpeaking();
|
|
243
|
+
if (state === 'done')
|
|
244
|
+
this.endUtterance();
|
|
245
|
+
else if (state === 'speaking' && this.vad.quietFrames === PAUSE_FRAMES)
|
|
246
|
+
this.checkTurn();
|
|
247
|
+
}
|
|
248
|
+
this.leftover = bytes.subarray(offset);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* The user talks over the AI: it goes quiet, and the user's sentence starts here (what came before was the
|
|
252
|
+
* AI's voice, which the echo filter would discard along with the user's words).
|
|
253
|
+
*/
|
|
254
|
+
bargeIn() {
|
|
255
|
+
this.log('the user is talking: cutting the AI off');
|
|
256
|
+
for (const said of this.said) {
|
|
257
|
+
if (said.until !== Infinity)
|
|
258
|
+
continue;
|
|
259
|
+
said.cut = true;
|
|
260
|
+
said.until = Date.now() + ECHO_WINDOW_MS;
|
|
261
|
+
}
|
|
262
|
+
this.interruptedAI = true;
|
|
263
|
+
// From here on, the current sentence belongs to the user.
|
|
264
|
+
this.startedWithAI = false;
|
|
265
|
+
if (this.vad.current === 'speaking')
|
|
266
|
+
this.vad.keepLast(BARGE_KEEP_FRAMES);
|
|
267
|
+
this.options.onBargeIn?.();
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* 0.3 s pause: Smart Turn tells whether the user has finished. If so and they are still quiet, the sentence
|
|
271
|
+
* closes now instead of after 1.5 s. If they started talking again meanwhile, the answer is ignored.
|
|
272
|
+
*/
|
|
273
|
+
checkTurn() {
|
|
274
|
+
const turnCheck = this.options.turnCheck;
|
|
275
|
+
// While the AI talks, what is heard is almost always its echo: the sentence closes when it stops talking.
|
|
276
|
+
if (!turnCheck || this.checkingTurn || this.vad.voicedMs < MIN_TURN_VOICED_MS || this.aiSpeaking())
|
|
277
|
+
return;
|
|
278
|
+
this.checkingTurn = true;
|
|
279
|
+
const id = this.utteranceId;
|
|
280
|
+
const started = Date.now();
|
|
281
|
+
turnCheck.complete(this.vad.utterance()).then((probability) => {
|
|
282
|
+
const still = id === this.utteranceId && this.vad.current === 'speaking' && this.vad.quietFrames >= PAUSE_FRAMES;
|
|
283
|
+
const verdict = probability > TURN_COMPLETE ? 'finished' : 'just a pause';
|
|
284
|
+
this.log(`Smart Turn: ${verdict} (${probability.toFixed(2)}, ${Date.now() - started} ms)${still ? '' : ' · outdated'}`);
|
|
285
|
+
if (still && probability > TURN_COMPLETE)
|
|
286
|
+
this.endUtterance();
|
|
287
|
+
}, (error) => this.log(`Smart Turn failed: ${error.message}`)).finally(() => (this.checkingTurn = false));
|
|
288
|
+
}
|
|
289
|
+
/** Closes the current sentence and prepares the next one; the detector keeps the room noise level. */
|
|
290
|
+
endUtterance() {
|
|
291
|
+
const audio = this.vad.utterance();
|
|
292
|
+
const voiced = this.vad.voicedMs;
|
|
293
|
+
const fromUser = !this.startedWithAI;
|
|
294
|
+
this.vad.next();
|
|
295
|
+
this.utteranceId++;
|
|
296
|
+
this.speaking = false;
|
|
297
|
+
const ms = Math.round((audio.length / 16_000) * 1000);
|
|
298
|
+
if (voiced >= MIN_VOICED_MS) {
|
|
299
|
+
this.log(`sentence closed: ${ms} ms of audio, ${voiced} ms of voice${this.aiSpeaking() ? ' (while the AI was talking)' : ''}`);
|
|
300
|
+
if (this.waiters.length && !this.aiSpeaking())
|
|
301
|
+
this.options.onPhrase?.();
|
|
302
|
+
this.transcribe(audio, fromUser, voiced, ms);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
this.log(`stray noise dropped: ${ms} ms, ${voiced} ms of voice`);
|
|
306
|
+
this.wake();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
log(message) {
|
|
310
|
+
(this.options.log ?? silent)(`[ear] ${message}`);
|
|
311
|
+
}
|
|
312
|
+
aiSpeaking() {
|
|
313
|
+
return this.said.some((said) => said.until === Infinity);
|
|
314
|
+
}
|
|
315
|
+
/** Sentences are transcribed in order, one after another. */
|
|
316
|
+
transcribe(audio, fromUser, voiced = Infinity, audioMs = Infinity) {
|
|
317
|
+
const generation = this.generation;
|
|
318
|
+
this.pending++;
|
|
319
|
+
this.transcribing = this.transcribing.then(async () => {
|
|
320
|
+
try {
|
|
321
|
+
if (this.options.speechCheck && !(await this.options.speechCheck.hasSpeech(audio))) {
|
|
322
|
+
this.log('Silero: not human voice → not transcribed');
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const text = (await this.options.transcriber.transcribe(audio)).trim();
|
|
326
|
+
if (generation !== this.generation)
|
|
327
|
+
return this.log(`dropped (the microphone closed meanwhile): «${text}»`);
|
|
328
|
+
if (!text)
|
|
329
|
+
return this.log('Whisper returned no text');
|
|
330
|
+
// What started while the AI was talking is almost always its echo: it is dropped if nearly all its words are
|
|
331
|
+
// the AI's, however short.
|
|
332
|
+
if (this.isEcho(text, fromUser ? ECHO_MIN_WORDS : 1))
|
|
333
|
+
return this.log(`AI echo dropped: «${text}»`);
|
|
334
|
+
if (isRepetition(text))
|
|
335
|
+
return this.log(`endless repetition dropped: «${text}»`);
|
|
336
|
+
if (!fromUser && echoLeftover(text, voiced, audioMs))
|
|
337
|
+
return this.log(`AI echo leftover dropped (${voiced} ms of voice in ${audioMs} ms): «${text}»`);
|
|
338
|
+
this.log(`heard: «${text}»`);
|
|
339
|
+
if (!fromUser) {
|
|
340
|
+
// It started while the AI was talking but is not its echo: the user is talking over it (their "stop" was not
|
|
341
|
+
// in what the AI said). This is the second way to cut the AI off, in case the microphone level check did not
|
|
342
|
+
// fire.
|
|
343
|
+
this.log('the user talked over the AI (by what they said)');
|
|
344
|
+
if (this.aiSpeaking())
|
|
345
|
+
this.bargeIn();
|
|
346
|
+
else
|
|
347
|
+
this.interruptedAI = true;
|
|
348
|
+
}
|
|
349
|
+
if (!this.heard.length)
|
|
350
|
+
this.heardAt = Date.now();
|
|
351
|
+
this.heard.push({ text, fromUser: true });
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
this.failure = `Could not transcribe the sentence: ${error instanceof Error ? error.message : String(error)}`;
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
this.pending--;
|
|
358
|
+
this.wake();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* With fewer than `minWords` words it is never echo (a user's "yes"); 1 for what started while the AI was
|
|
364
|
+
* talking.
|
|
365
|
+
*/
|
|
366
|
+
isEcho(text, minWords = ECHO_MIN_WORDS) {
|
|
367
|
+
const now = Date.now();
|
|
368
|
+
this.said = this.said.filter((said) => said.until > now);
|
|
369
|
+
const heard = words(text);
|
|
370
|
+
if (heard.size < minWords)
|
|
371
|
+
return false;
|
|
372
|
+
return this.said.some((said) => {
|
|
373
|
+
let shared = 0;
|
|
374
|
+
for (const word of heard)
|
|
375
|
+
if (said.words.has(word))
|
|
376
|
+
shared++;
|
|
377
|
+
return shared / heard.size >= ECHO_OVERLAP;
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
wake() {
|
|
381
|
+
const waiters = this.waiters;
|
|
382
|
+
this.waiters = [];
|
|
383
|
+
for (const waiter of waiters)
|
|
384
|
+
waiter();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=ear.js.map
|
package/dist/ear.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ear.js","sourceRoot":"","sources":["../src/ear.ts"],"names":[],"mappings":"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"}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Logs what happens to each sentence. Without --verbose it logs nothing. */
|
|
2
|
+
export type Log = (message: string) => void;
|
|
3
|
+
export declare const silent: Log;
|
|
4
|
+
/** To stderr (stdout is the MCP channel) and to the file, with the time. */
|
|
5
|
+
export declare function fileLog(file: string): Log;
|
|
6
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAGA,6EAA6E;AAC7E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5C,eAAO,MAAM,MAAM,EAAE,GAAc,CAAC;AAEpC,4EAA4E;AAC5E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAOzC"}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export const silent = () => { };
|
|
4
|
+
/** To stderr (stdout is the MCP channel) and to the file, with the time. */
|
|
5
|
+
export function fileLog(file) {
|
|
6
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
7
|
+
return (message) => {
|
|
8
|
+
const line = `${new Date().toISOString().slice(11, 23)} ${message}`;
|
|
9
|
+
console.error(line);
|
|
10
|
+
appendFileSync(file, line + '\n');
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,MAAM,CAAC,MAAM,MAAM,GAAQ,GAAG,EAAE,GAAE,CAAC,CAAC;AAEpC,4EAA4E;AAC5E,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO,CAAC,OAAO,EAAE,EAAE;QACjB,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,OAAO,EAAE,CAAC;QACpE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACpB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;IACpC,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/mic.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** The sample rate Whisper expects; the microphone is recorded at it directly. */
|
|
2
|
+
export declare const SAMPLE_RATE = 16000;
|
|
3
|
+
/** The open microphone: 16-bit PCM, mono, 16 kHz. */
|
|
4
|
+
export interface Mic {
|
|
5
|
+
onData(listener: (chunk: Buffer) => void): void;
|
|
6
|
+
onExit(listener: (reason: string) => void): void;
|
|
7
|
+
stop(): void;
|
|
8
|
+
}
|
|
9
|
+
export type OpenMic = () => Mic;
|
|
10
|
+
/** How ffmpeg opens the microphone on each operating system. */
|
|
11
|
+
export declare function inputArgs(platform: NodeJS.Platform, device: string | undefined): string[];
|
|
12
|
+
/** On Windows the microphone must be named: the first one ffmpeg lists. */
|
|
13
|
+
export declare function findWindowsMic(ffmpeg: string): string | undefined;
|
|
14
|
+
/** The computer microphone through ffmpeg. It is only open while listening. */
|
|
15
|
+
export declare function createFfmpegMic({ ffmpeg, device }: {
|
|
16
|
+
ffmpeg: string;
|
|
17
|
+
device?: string;
|
|
18
|
+
}): OpenMic;
|
|
19
|
+
//# sourceMappingURL=mic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mic.d.ts","sourceRoot":"","sources":["../src/mic.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,eAAO,MAAM,WAAW,QAAS,CAAC;AAElC,qDAAqD;AACrD,MAAM,WAAW,GAAG;IAClB,MAAM,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,MAAM,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IACjD,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC;AAEhC,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAIzF;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAwBhG"}
|