@komaa/standin-sdk 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 +141 -0
- package/dist/ambient.d.ts +80 -0
- package/dist/ambient.d.ts.map +1 -0
- package/dist/ambient.js +281 -0
- package/dist/ambient.js.map +1 -0
- package/dist/attachments.d.ts +149 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +451 -0
- package/dist/attachments.js.map +1 -0
- package/dist/audio.d.ts +80 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +170 -0
- package/dist/audio.js.map +1 -0
- package/dist/avatar.d.ts +111 -0
- package/dist/avatar.d.ts.map +1 -0
- package/dist/avatar.js +201 -0
- package/dist/avatar.js.map +1 -0
- package/dist/callServer.d.ts +151 -0
- package/dist/callServer.d.ts.map +1 -0
- package/dist/callServer.js +1097 -0
- package/dist/callServer.js.map +1 -0
- package/dist/callTools.d.ts +154 -0
- package/dist/callTools.d.ts.map +1 -0
- package/dist/callTools.js +255 -0
- package/dist/callTools.js.map +1 -0
- package/dist/chat.d.ts +226 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +574 -0
- package/dist/chat.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +86 -0
- package/dist/config.js.map +1 -0
- package/dist/consult.d.ts +147 -0
- package/dist/consult.d.ts.map +1 -0
- package/dist/consult.js +388 -0
- package/dist/consult.js.map +1 -0
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +187 -0
- package/dist/delivery.js.map +1 -0
- package/dist/echoGuard.d.ts +56 -0
- package/dist/echoGuard.d.ts.map +1 -0
- package/dist/echoGuard.js +57 -0
- package/dist/echoGuard.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/fetch.d.ts +40 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +240 -0
- package/dist/fetch.js.map +1 -0
- package/dist/gate.d.ts +80 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +249 -0
- package/dist/gate.js.map +1 -0
- package/dist/handler.d.ts +283 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +4 -0
- package/dist/handler.js.map +1 -0
- package/dist/hmac.d.ts +32 -0
- package/dist/hmac.d.ts.map +1 -0
- package/dist/hmac.js +108 -0
- package/dist/hmac.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/lane.d.ts +92 -0
- package/dist/lane.d.ts.map +1 -0
- package/dist/lane.js +269 -0
- package/dist/lane.js.map +1 -0
- package/dist/lipsync.d.ts +136 -0
- package/dist/lipsync.d.ts.map +1 -0
- package/dist/lipsync.js +310 -0
- package/dist/lipsync.js.map +1 -0
- package/dist/log.d.ts +18 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +20 -0
- package/dist/log.js.map +1 -0
- package/dist/media.d.ts +43 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +190 -0
- package/dist/media.js.map +1 -0
- package/dist/minutes.d.ts +398 -0
- package/dist/minutes.d.ts.map +1 -0
- package/dist/minutes.js +805 -0
- package/dist/minutes.js.map +1 -0
- package/dist/outbound.d.ts +388 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +933 -0
- package/dist/outbound.js.map +1 -0
- package/dist/plugins/cartesia/agent.d.ts +58 -0
- package/dist/plugins/cartesia/agent.d.ts.map +1 -0
- package/dist/plugins/cartesia/agent.js +254 -0
- package/dist/plugins/cartesia/agent.js.map +1 -0
- package/dist/plugins/cartesia/cli.d.ts +3 -0
- package/dist/plugins/cartesia/cli.d.ts.map +1 -0
- package/dist/plugins/cartesia/cli.js +10 -0
- package/dist/plugins/cartesia/cli.js.map +1 -0
- package/dist/plugins/cartesia/config.d.ts +30 -0
- package/dist/plugins/cartesia/config.d.ts.map +1 -0
- package/dist/plugins/cartesia/config.js +29 -0
- package/dist/plugins/cartesia/config.js.map +1 -0
- package/dist/plugins/cartesia/handler.d.ts +40 -0
- package/dist/plugins/cartesia/handler.d.ts.map +1 -0
- package/dist/plugins/cartesia/handler.js +142 -0
- package/dist/plugins/cartesia/handler.js.map +1 -0
- package/dist/plugins/cartesia/index.d.ts +6 -0
- package/dist/plugins/cartesia/index.d.ts.map +1 -0
- package/dist/plugins/cartesia/index.js +54 -0
- package/dist/plugins/cartesia/index.js.map +1 -0
- package/dist/plugins/deepgram/agent.d.ts +60 -0
- package/dist/plugins/deepgram/agent.d.ts.map +1 -0
- package/dist/plugins/deepgram/agent.js +302 -0
- package/dist/plugins/deepgram/agent.js.map +1 -0
- package/dist/plugins/deepgram/cli.d.ts +3 -0
- package/dist/plugins/deepgram/cli.d.ts.map +1 -0
- package/dist/plugins/deepgram/cli.js +10 -0
- package/dist/plugins/deepgram/cli.js.map +1 -0
- package/dist/plugins/deepgram/config.d.ts +39 -0
- package/dist/plugins/deepgram/config.d.ts.map +1 -0
- package/dist/plugins/deepgram/config.js +66 -0
- package/dist/plugins/deepgram/config.js.map +1 -0
- package/dist/plugins/deepgram/handler.d.ts +87 -0
- package/dist/plugins/deepgram/handler.d.ts.map +1 -0
- package/dist/plugins/deepgram/handler.js +268 -0
- package/dist/plugins/deepgram/handler.js.map +1 -0
- package/dist/plugins/deepgram/index.d.ts +11 -0
- package/dist/plugins/deepgram/index.d.ts.map +1 -0
- package/dist/plugins/deepgram/index.js +76 -0
- package/dist/plugins/deepgram/index.js.map +1 -0
- package/dist/plugins/echo/cli.d.ts +3 -0
- package/dist/plugins/echo/cli.d.ts.map +1 -0
- package/dist/plugins/echo/cli.js +10 -0
- package/dist/plugins/echo/cli.js.map +1 -0
- package/dist/plugins/echo/index.d.ts +37 -0
- package/dist/plugins/echo/index.d.ts.map +1 -0
- package/dist/plugins/echo/index.js +61 -0
- package/dist/plugins/echo/index.js.map +1 -0
- package/dist/plugins/elevenlabs/agent.d.ts +72 -0
- package/dist/plugins/elevenlabs/agent.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/agent.js +305 -0
- package/dist/plugins/elevenlabs/agent.js.map +1 -0
- package/dist/plugins/elevenlabs/cli.d.ts +3 -0
- package/dist/plugins/elevenlabs/cli.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/cli.js +10 -0
- package/dist/plugins/elevenlabs/cli.js.map +1 -0
- package/dist/plugins/elevenlabs/config.d.ts +27 -0
- package/dist/plugins/elevenlabs/config.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/config.js +42 -0
- package/dist/plugins/elevenlabs/config.js.map +1 -0
- package/dist/plugins/elevenlabs/handler.d.ts +67 -0
- package/dist/plugins/elevenlabs/handler.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/handler.js +419 -0
- package/dist/plugins/elevenlabs/handler.js.map +1 -0
- package/dist/plugins/elevenlabs/index.d.ts +11 -0
- package/dist/plugins/elevenlabs/index.d.ts.map +1 -0
- package/dist/plugins/elevenlabs/index.js +72 -0
- package/dist/plugins/elevenlabs/index.js.map +1 -0
- package/dist/plugins/livekit/cli.d.ts +3 -0
- package/dist/plugins/livekit/cli.d.ts.map +1 -0
- package/dist/plugins/livekit/cli.js +10 -0
- package/dist/plugins/livekit/cli.js.map +1 -0
- package/dist/plugins/livekit/config.d.ts +47 -0
- package/dist/plugins/livekit/config.d.ts.map +1 -0
- package/dist/plugins/livekit/config.js +46 -0
- package/dist/plugins/livekit/config.js.map +1 -0
- package/dist/plugins/livekit/handler.d.ts +59 -0
- package/dist/plugins/livekit/handler.d.ts.map +1 -0
- package/dist/plugins/livekit/handler.js +211 -0
- package/dist/plugins/livekit/handler.js.map +1 -0
- package/dist/plugins/livekit/index.d.ts +6 -0
- package/dist/plugins/livekit/index.d.ts.map +1 -0
- package/dist/plugins/livekit/index.js +68 -0
- package/dist/plugins/livekit/index.js.map +1 -0
- package/dist/plugins/livekit/room.d.ts +117 -0
- package/dist/plugins/livekit/room.d.ts.map +1 -0
- package/dist/plugins/livekit/room.js +449 -0
- package/dist/plugins/livekit/room.js.map +1 -0
- package/dist/plugins/openai/agent.d.ts +74 -0
- package/dist/plugins/openai/agent.d.ts.map +1 -0
- package/dist/plugins/openai/agent.js +264 -0
- package/dist/plugins/openai/agent.js.map +1 -0
- package/dist/plugins/openai/cli.d.ts +3 -0
- package/dist/plugins/openai/cli.d.ts.map +1 -0
- package/dist/plugins/openai/cli.js +10 -0
- package/dist/plugins/openai/cli.js.map +1 -0
- package/dist/plugins/openai/config.d.ts +29 -0
- package/dist/plugins/openai/config.d.ts.map +1 -0
- package/dist/plugins/openai/config.js +37 -0
- package/dist/plugins/openai/config.js.map +1 -0
- package/dist/plugins/openai/handler.d.ts +65 -0
- package/dist/plugins/openai/handler.d.ts.map +1 -0
- package/dist/plugins/openai/handler.js +297 -0
- package/dist/plugins/openai/handler.js.map +1 -0
- package/dist/plugins/openai/index.d.ts +6 -0
- package/dist/plugins/openai/index.d.ts.map +1 -0
- package/dist/plugins/openai/index.js +72 -0
- package/dist/plugins/openai/index.js.map +1 -0
- package/dist/plugins/openclaw/allowlist.d.ts +9 -0
- package/dist/plugins/openclaw/allowlist.d.ts.map +1 -0
- package/dist/plugins/openclaw/allowlist.js +11 -0
- package/dist/plugins/openclaw/allowlist.js.map +1 -0
- package/dist/plugins/openclaw/config.d.ts +50 -0
- package/dist/plugins/openclaw/config.d.ts.map +1 -0
- package/dist/plugins/openclaw/config.js +53 -0
- package/dist/plugins/openclaw/config.js.map +1 -0
- package/dist/plugins/openclaw/handler.d.ts +90 -0
- package/dist/plugins/openclaw/handler.d.ts.map +1 -0
- package/dist/plugins/openclaw/handler.js +146 -0
- package/dist/plugins/openclaw/handler.js.map +1 -0
- package/dist/plugins/openclaw/index.d.ts +55 -0
- package/dist/plugins/openclaw/index.d.ts.map +1 -0
- package/dist/plugins/openclaw/index.js +89 -0
- package/dist/plugins/openclaw/index.js.map +1 -0
- package/dist/plugins/openclaw/openclaw.plugin.json +74 -0
- package/dist/plugins/openclaw/package.json +14 -0
- package/dist/plugins/openclaw/realtime.d.ts +82 -0
- package/dist/plugins/openclaw/realtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/realtime.js +238 -0
- package/dist/plugins/openclaw/realtime.js.map +1 -0
- package/dist/plugins/openclaw/runtime.d.ts +19 -0
- package/dist/plugins/openclaw/runtime.d.ts.map +1 -0
- package/dist/plugins/openclaw/runtime.js +160 -0
- package/dist/plugins/openclaw/runtime.js.map +1 -0
- package/dist/policy.d.ts +38 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +95 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol.d.ts +44 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +77 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocolRuntime.d.ts +19 -0
- package/dist/protocolRuntime.d.ts.map +1 -0
- package/dist/protocolRuntime.js +78 -0
- package/dist/protocolRuntime.js.map +1 -0
- package/dist/smoke.d.ts +65 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +293 -0
- package/dist/smoke.js.map +1 -0
- package/dist/startup.d.ts +74 -0
- package/dist/startup.d.ts.map +1 -0
- package/dist/startup.js +112 -0
- package/dist/startup.js.map +1 -0
- package/dist/tile.d.ts +101 -0
- package/dist/tile.d.ts.map +1 -0
- package/dist/tile.js +195 -0
- package/dist/tile.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/dist/vision.d.ts +160 -0
- package/dist/vision.d.ts.map +1 -0
- package/dist/vision.js +301 -0
- package/dist/vision.js.map +1 -0
- package/dist/visionTools.d.ts +237 -0
- package/dist/visionTools.d.ts.map +1 -0
- package/dist/visionTools.js +578 -0
- package/dist/visionTools.js.map +1 -0
- package/dist/voice.d.ts +143 -0
- package/dist/voice.d.ts.map +1 -0
- package/dist/voice.js +403 -0
- package/dist/voice.js.map +1 -0
- package/package.json +129 -0
package/dist/voice.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loudness at which a frame counts as speech rather than room noise. The same
|
|
3
|
+
* scale {@link pcm16Rms} returns, 0.0 to 1.0.
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_SPEECH_RMS = 0.02;
|
|
6
|
+
/**
|
|
7
|
+
* How much quiet ends an utterance. Short enough that the agent does not feel
|
|
8
|
+
* slow, long enough to survive the pause in the middle of a sentence.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DEFAULT_SILENCE_MS = 800;
|
|
11
|
+
/**
|
|
12
|
+
* Audio kept from BEFORE the gate opened. The syllable that trips the gate is
|
|
13
|
+
* part of the word, and without this every utterance starts clipped.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DEFAULT_PREROLL_MS = 240;
|
|
16
|
+
/**
|
|
17
|
+
* Below this an utterance is a cough, a door, a chair. Transcribing it costs a
|
|
18
|
+
* request and returns nothing worth answering.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DEFAULT_MIN_UTTERANCE_MS = 120;
|
|
21
|
+
/**
|
|
22
|
+
* A hard ceiling, so a stuck-open microphone or a television in the room does
|
|
23
|
+
* not grow one utterance for the length of the call.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_MAX_UTTERANCE_MS = 30000;
|
|
26
|
+
/** Options for {@link UtteranceSegmenter}. */
|
|
27
|
+
export interface SegmenterOptions {
|
|
28
|
+
speechRms?: number;
|
|
29
|
+
silenceMs?: number;
|
|
30
|
+
prerollMs?: number;
|
|
31
|
+
minUtteranceMs?: number;
|
|
32
|
+
maxUtteranceMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* One utterance per spoken phrase, out of a continuous stream.
|
|
36
|
+
*
|
|
37
|
+
* A Microsoft Teams call delivers audio continuously: silence is still frames,
|
|
38
|
+
* fifty a second. A transcription service wants a phrase. This is the gate
|
|
39
|
+
* between them.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const utterance = segmenter.feed(frame);
|
|
43
|
+
* if (utterance !== undefined) await transcribe(utterance);
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* Four bounds, and each exists because of a specific failure.
|
|
47
|
+
*
|
|
48
|
+
* **Pre-roll**, because the syllable that trips the gate is part of the word.
|
|
49
|
+
* Without it every utterance begins mid-consonant and the transcript loses the
|
|
50
|
+
* first word of most sentences.
|
|
51
|
+
*
|
|
52
|
+
* **Trailing silence**, because a pause inside a sentence is not the end of one.
|
|
53
|
+
* Too short and the agent interrupts a thinking caller; too long and it feels
|
|
54
|
+
* slow.
|
|
55
|
+
*
|
|
56
|
+
* **A floor**, because a cough is not a turn. Sending one costs a request and
|
|
57
|
+
* returns nothing worth answering.
|
|
58
|
+
*
|
|
59
|
+
* **A ceiling**, because a stuck-open microphone or a television in the room
|
|
60
|
+
* never goes quiet, and without a cap one utterance grows for the whole call.
|
|
61
|
+
*/
|
|
62
|
+
export declare class UtteranceSegmenter {
|
|
63
|
+
#private;
|
|
64
|
+
readonly speechRms: number;
|
|
65
|
+
readonly silenceMs: number;
|
|
66
|
+
readonly minUtteranceMs: number;
|
|
67
|
+
readonly maxUtteranceMs: number;
|
|
68
|
+
constructor(options?: SegmenterOptions);
|
|
69
|
+
/** Whether the caller is mid-utterance right now. */
|
|
70
|
+
get speaking(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Take one frame. Returns a finished utterance, or undefined.
|
|
73
|
+
*
|
|
74
|
+
* Never throws and never blocks: it runs on the receive path of a live call,
|
|
75
|
+
* once per frame.
|
|
76
|
+
*/
|
|
77
|
+
feed(pcm: Buffer): Buffer | undefined;
|
|
78
|
+
/** Take whatever is held, mid-utterance. For teardown, or a barge-in. */
|
|
79
|
+
flush(): Buffer | undefined;
|
|
80
|
+
/** Forget everything. The caller interrupted, or the turn is abandoned. */
|
|
81
|
+
reset(): void;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read a WAV and return PCM16 mono at the call's rate.
|
|
85
|
+
*
|
|
86
|
+
* A speech engine hands you a WAV and it is rarely the one you wanted: 32-bit
|
|
87
|
+
* float, 44.1 kHz, stereo, or wrapped in `WAVE_FORMAT_EXTENSIBLE`, which ffmpeg
|
|
88
|
+
* emits even for plain PCM. Every one of those plays as noise if you put it on a
|
|
89
|
+
* call unconverted, and the failure is not obvious from the header.
|
|
90
|
+
*
|
|
91
|
+
* Chunks are walked rather than assumed at a fixed offset, because a real
|
|
92
|
+
* encoder puts `LIST` and `fact` chunks before the data and a fixed offset reads
|
|
93
|
+
* them as samples.
|
|
94
|
+
*
|
|
95
|
+
* Throws on anything that is not a WAV this can read.
|
|
96
|
+
*/
|
|
97
|
+
export declare function decodeWav(data: Buffer): Buffer;
|
|
98
|
+
/** Wrap PCM16 mono in a WAV, for a service that will not take raw PCM. */
|
|
99
|
+
export declare function encodeWav(pcm: Buffer, sampleRateHz?: number): Buffer;
|
|
100
|
+
/** What happened to one buffer handed to {@link PacedPlayback}. */
|
|
101
|
+
export interface Playback {
|
|
102
|
+
/** How much actually reached the caller. */
|
|
103
|
+
readonly sentMs: number;
|
|
104
|
+
/** How much there was. */
|
|
105
|
+
readonly totalMs: number;
|
|
106
|
+
/** Whether it was cut short. */
|
|
107
|
+
readonly interrupted: boolean;
|
|
108
|
+
}
|
|
109
|
+
/** Hand one wire frame to the call. Normally `session.sendAudio`. */
|
|
110
|
+
export type FrameSink = (pcm: Buffer) => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Feed a finished buffer out at the rate a call consumes it.
|
|
113
|
+
*
|
|
114
|
+
* A text-to-speech engine returns a whole utterance at once. A call takes 20
|
|
115
|
+
* milliseconds every 20 milliseconds. Sending the buffer in one go hands the
|
|
116
|
+
* service seconds of audio it must queue, and the queue is what makes a barge-in
|
|
117
|
+
* arrive too late to matter: the caller interrupts, the model stops, and the bot
|
|
118
|
+
* keeps talking for the length of what was already sent.
|
|
119
|
+
*
|
|
120
|
+
* So it goes out paced, and the pacing is on an ABSOLUTE clock. Sleeping 20 ms
|
|
121
|
+
* per frame accumulates every scheduling delay, and a minute of speech ends
|
|
122
|
+
* seconds behind where it should be; this one waits until the next frame is DUE,
|
|
123
|
+
* so a late frame is followed by a short wait rather than a full one.
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* const playback = new PacedPlayback((pcm) => session.sendAudio(pcm));
|
|
127
|
+
* const result = await playback.say(pcm);
|
|
128
|
+
* if (result.interrupted) { ... }
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* {@link cancel} stops whatever is playing. The result says how much was heard,
|
|
132
|
+
* which is the difference between "I told them" and "I started to".
|
|
133
|
+
*/
|
|
134
|
+
export declare class PacedPlayback {
|
|
135
|
+
#private;
|
|
136
|
+
constructor(send: FrameSink, frameMs?: number);
|
|
137
|
+
get playing(): boolean;
|
|
138
|
+
/** Stop what is playing. Safe from anywhere, including a receive loop. */
|
|
139
|
+
cancel(): void;
|
|
140
|
+
/** Play one buffer and return what the caller actually heard. */
|
|
141
|
+
say(pcm: Buffer): Promise<Playback>;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=voice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice.d.ts","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,kBAAkB;;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAepB,OAAO,GAAE,gBAAqB;IAW1C,qDAAqD;IACrD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;;OAKG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAmCrC,yEAAyE;IACzE,KAAK,IAAI,MAAM,GAAG,SAAS;IAI3B,2EAA2E;IAC3E,KAAK,IAAI,IAAI;CA+Bd;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA0D9C;AAED,0EAA0E;AAC1E,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,MAAuB,GACpC,MAAM,CAgBR;AA+BD,mEAAmE;AACnE,MAAM,WAAW,QAAQ;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,qEAAqE;AACrE,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,aAAa;;gBAUZ,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,MAAiB;IAMvD,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,0EAA0E;IAC1E,MAAM,IAAI,IAAI;IAId,iEAAiE;IAC3D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CA8C1C"}
|
package/dist/voice.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// Copyright (c) 2026 Komaa DigiTech
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* Turn-taking for an agent that is not a realtime model.
|
|
5
|
+
*
|
|
6
|
+
* A realtime speech-to-speech provider is handed the caller's audio and hands
|
|
7
|
+
* audio back, and the turn-taking is theirs. Everything else is not like that. A
|
|
8
|
+
* transcription service wants one utterance at a time, a language model wants
|
|
9
|
+
* text, and a text-to-speech engine hands back a whole buffer that somebody has
|
|
10
|
+
* to feed out at the rate a call consumes it.
|
|
11
|
+
*
|
|
12
|
+
* That shape is the same whichever three services you pick, and four separate
|
|
13
|
+
* bridges each wrote it before this module existed. Three pieces:
|
|
14
|
+
*
|
|
15
|
+
* {@link UtteranceSegmenter} turns fifty frames a second into one utterance per
|
|
16
|
+
* spoken phrase. {@link decodeWav} and {@link encodeWav} deal with the fact that
|
|
17
|
+
* a speech engine hands you a WAV and it is rarely the WAV you wanted.
|
|
18
|
+
* {@link PacedPlayback} feeds a finished buffer out at the rate the call
|
|
19
|
+
* consumes it, interruptibly.
|
|
20
|
+
*
|
|
21
|
+
* None of this is needed by a realtime plugin, and none of it costs one
|
|
22
|
+
* anything: it is a module you do not import.
|
|
23
|
+
*
|
|
24
|
+
* Identical in shape to the Python SDK's `standin.voice`.
|
|
25
|
+
*/
|
|
26
|
+
import { FRAME_BYTES, FRAME_MS, pcm16Rms, resamplePcm16 } from "./audio.js";
|
|
27
|
+
import { logger } from "./log.js";
|
|
28
|
+
import { SAMPLE_RATE_HZ } from "./protocol.js";
|
|
29
|
+
/**
|
|
30
|
+
* Loudness at which a frame counts as speech rather than room noise. The same
|
|
31
|
+
* scale {@link pcm16Rms} returns, 0.0 to 1.0.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_SPEECH_RMS = 0.02;
|
|
34
|
+
/**
|
|
35
|
+
* How much quiet ends an utterance. Short enough that the agent does not feel
|
|
36
|
+
* slow, long enough to survive the pause in the middle of a sentence.
|
|
37
|
+
*/
|
|
38
|
+
export const DEFAULT_SILENCE_MS = 800;
|
|
39
|
+
/**
|
|
40
|
+
* Audio kept from BEFORE the gate opened. The syllable that trips the gate is
|
|
41
|
+
* part of the word, and without this every utterance starts clipped.
|
|
42
|
+
*/
|
|
43
|
+
export const DEFAULT_PREROLL_MS = 240;
|
|
44
|
+
/**
|
|
45
|
+
* Below this an utterance is a cough, a door, a chair. Transcribing it costs a
|
|
46
|
+
* request and returns nothing worth answering.
|
|
47
|
+
*/
|
|
48
|
+
export const DEFAULT_MIN_UTTERANCE_MS = 120;
|
|
49
|
+
/**
|
|
50
|
+
* A hard ceiling, so a stuck-open microphone or a television in the room does
|
|
51
|
+
* not grow one utterance for the length of the call.
|
|
52
|
+
*/
|
|
53
|
+
export const DEFAULT_MAX_UTTERANCE_MS = 30_000;
|
|
54
|
+
/**
|
|
55
|
+
* One utterance per spoken phrase, out of a continuous stream.
|
|
56
|
+
*
|
|
57
|
+
* A Microsoft Teams call delivers audio continuously: silence is still frames,
|
|
58
|
+
* fifty a second. A transcription service wants a phrase. This is the gate
|
|
59
|
+
* between them.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const utterance = segmenter.feed(frame);
|
|
63
|
+
* if (utterance !== undefined) await transcribe(utterance);
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Four bounds, and each exists because of a specific failure.
|
|
67
|
+
*
|
|
68
|
+
* **Pre-roll**, because the syllable that trips the gate is part of the word.
|
|
69
|
+
* Without it every utterance begins mid-consonant and the transcript loses the
|
|
70
|
+
* first word of most sentences.
|
|
71
|
+
*
|
|
72
|
+
* **Trailing silence**, because a pause inside a sentence is not the end of one.
|
|
73
|
+
* Too short and the agent interrupts a thinking caller; too long and it feels
|
|
74
|
+
* slow.
|
|
75
|
+
*
|
|
76
|
+
* **A floor**, because a cough is not a turn. Sending one costs a request and
|
|
77
|
+
* returns nothing worth answering.
|
|
78
|
+
*
|
|
79
|
+
* **A ceiling**, because a stuck-open microphone or a television in the room
|
|
80
|
+
* never goes quiet, and without a cap one utterance grows for the whole call.
|
|
81
|
+
*/
|
|
82
|
+
export class UtteranceSegmenter {
|
|
83
|
+
speechRms;
|
|
84
|
+
silenceMs;
|
|
85
|
+
minUtteranceMs;
|
|
86
|
+
maxUtteranceMs;
|
|
87
|
+
#prerollFrames;
|
|
88
|
+
// A ring, not a growing list: it holds pre-speech audio on every frame of a
|
|
89
|
+
// silent call, which is most of them.
|
|
90
|
+
#preroll = [];
|
|
91
|
+
#speech = [];
|
|
92
|
+
#quietMs = 0;
|
|
93
|
+
#speaking = false;
|
|
94
|
+
/**
|
|
95
|
+
* Frames since the gate opened, and the last of them that was loud. The gap
|
|
96
|
+
* between the two is trailing silence, which is not speech.
|
|
97
|
+
*/
|
|
98
|
+
#sinceOpen = 0;
|
|
99
|
+
#lastLoud = 0;
|
|
100
|
+
constructor(options = {}) {
|
|
101
|
+
this.speechRms = options.speechRms ?? DEFAULT_SPEECH_RMS;
|
|
102
|
+
this.silenceMs = options.silenceMs ?? DEFAULT_SILENCE_MS;
|
|
103
|
+
this.minUtteranceMs = options.minUtteranceMs ?? DEFAULT_MIN_UTTERANCE_MS;
|
|
104
|
+
this.maxUtteranceMs = options.maxUtteranceMs ?? DEFAULT_MAX_UTTERANCE_MS;
|
|
105
|
+
this.#prerollFrames = Math.max(1, Math.floor((options.prerollMs ?? DEFAULT_PREROLL_MS) / FRAME_MS));
|
|
106
|
+
}
|
|
107
|
+
/** Whether the caller is mid-utterance right now. */
|
|
108
|
+
get speaking() {
|
|
109
|
+
return this.#speaking;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Take one frame. Returns a finished utterance, or undefined.
|
|
113
|
+
*
|
|
114
|
+
* Never throws and never blocks: it runs on the receive path of a live call,
|
|
115
|
+
* once per frame.
|
|
116
|
+
*/
|
|
117
|
+
feed(pcm) {
|
|
118
|
+
if (pcm.length === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
const loud = pcm16Rms(pcm) >= this.speechRms;
|
|
121
|
+
if (!this.#speaking) {
|
|
122
|
+
if (!loud) {
|
|
123
|
+
this.#preroll.push(pcm);
|
|
124
|
+
if (this.#preroll.length > this.#prerollFrames)
|
|
125
|
+
this.#preroll.shift();
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
// Opening: the ring is what the caller actually started saying.
|
|
129
|
+
this.#speech = [...this.#preroll, pcm];
|
|
130
|
+
this.#sinceOpen = 1;
|
|
131
|
+
this.#lastLoud = 1;
|
|
132
|
+
this.#preroll = [];
|
|
133
|
+
this.#speaking = true;
|
|
134
|
+
this.#quietMs = 0;
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
this.#speech.push(pcm);
|
|
138
|
+
this.#sinceOpen += 1;
|
|
139
|
+
if (loud)
|
|
140
|
+
this.#lastLoud = this.#sinceOpen;
|
|
141
|
+
this.#quietMs = loud ? 0 : this.#quietMs + FRAME_MS;
|
|
142
|
+
if (this.#quietMs >= this.silenceMs)
|
|
143
|
+
return this.#finish(false);
|
|
144
|
+
if (this.#durationMs() >= this.maxUtteranceMs) {
|
|
145
|
+
logger.info(`standin: cutting an utterance at its ${this.maxUtteranceMs} ms ceiling`);
|
|
146
|
+
return this.#finish(true);
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
/** Take whatever is held, mid-utterance. For teardown, or a barge-in. */
|
|
151
|
+
flush() {
|
|
152
|
+
return this.#speaking ? this.#finish(false) : undefined;
|
|
153
|
+
}
|
|
154
|
+
/** Forget everything. The caller interrupted, or the turn is abandoned. */
|
|
155
|
+
reset() {
|
|
156
|
+
this.#speech = [];
|
|
157
|
+
this.#preroll = [];
|
|
158
|
+
this.#quietMs = 0;
|
|
159
|
+
this.#speaking = false;
|
|
160
|
+
this.#sinceOpen = 0;
|
|
161
|
+
this.#lastLoud = 0;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Everything held, pre-roll and trailing silence included.
|
|
165
|
+
*
|
|
166
|
+
* The right measure for the ceiling: a stuck-open microphone is filling
|
|
167
|
+
* memory whether or not anybody is talking into it.
|
|
168
|
+
*/
|
|
169
|
+
#durationMs() {
|
|
170
|
+
return this.#speech.length * FRAME_MS;
|
|
171
|
+
}
|
|
172
|
+
/** The loud part alone, which is what a floor has to judge. */
|
|
173
|
+
#voicedMs() {
|
|
174
|
+
return this.#lastLoud * FRAME_MS;
|
|
175
|
+
}
|
|
176
|
+
#finish(force) {
|
|
177
|
+
const audio = Buffer.concat(this.#speech);
|
|
178
|
+
const voiced = this.#voicedMs();
|
|
179
|
+
this.reset();
|
|
180
|
+
if (!force && voiced < this.minUtteranceMs)
|
|
181
|
+
return undefined;
|
|
182
|
+
return audio.length > 0 ? audio : undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// -------------------------------------------------------------------- the WAV
|
|
186
|
+
/** Plain integer PCM. */
|
|
187
|
+
const FORMAT_PCM = 1;
|
|
188
|
+
/** IEEE float, which several engines emit at 32 bits. */
|
|
189
|
+
const FORMAT_FLOAT = 3;
|
|
190
|
+
/**
|
|
191
|
+
* The wrapper ffmpeg and several speech engines emit even for plain PCM. The
|
|
192
|
+
* real format is a GUID in the extension, whose first two bytes are the tag
|
|
193
|
+
* above, so reading those two bytes is enough.
|
|
194
|
+
*/
|
|
195
|
+
const FORMAT_EXTENSIBLE = 0xfffe;
|
|
196
|
+
/**
|
|
197
|
+
* Read a WAV and return PCM16 mono at the call's rate.
|
|
198
|
+
*
|
|
199
|
+
* A speech engine hands you a WAV and it is rarely the one you wanted: 32-bit
|
|
200
|
+
* float, 44.1 kHz, stereo, or wrapped in `WAVE_FORMAT_EXTENSIBLE`, which ffmpeg
|
|
201
|
+
* emits even for plain PCM. Every one of those plays as noise if you put it on a
|
|
202
|
+
* call unconverted, and the failure is not obvious from the header.
|
|
203
|
+
*
|
|
204
|
+
* Chunks are walked rather than assumed at a fixed offset, because a real
|
|
205
|
+
* encoder puts `LIST` and `fact` chunks before the data and a fixed offset reads
|
|
206
|
+
* them as samples.
|
|
207
|
+
*
|
|
208
|
+
* Throws on anything that is not a WAV this can read.
|
|
209
|
+
*/
|
|
210
|
+
export function decodeWav(data) {
|
|
211
|
+
if (data.length < 12 ||
|
|
212
|
+
data.toString("ascii", 0, 4) !== "RIFF" ||
|
|
213
|
+
data.toString("ascii", 8, 12) !== "WAVE") {
|
|
214
|
+
throw new Error("not a RIFF/WAVE file");
|
|
215
|
+
}
|
|
216
|
+
let audioFormat = 0;
|
|
217
|
+
let channels = 0;
|
|
218
|
+
let rate = 0;
|
|
219
|
+
let bits = 0;
|
|
220
|
+
let payload;
|
|
221
|
+
let at = 12;
|
|
222
|
+
while (at + 8 <= data.length) {
|
|
223
|
+
const chunkId = data.toString("ascii", at, at + 4);
|
|
224
|
+
const size = data.readUInt32LE(at + 4);
|
|
225
|
+
const body = data.subarray(at + 8, at + 8 + size);
|
|
226
|
+
if (chunkId === "fmt " && body.length >= 16) {
|
|
227
|
+
audioFormat = body.readUInt16LE(0);
|
|
228
|
+
channels = body.readUInt16LE(2);
|
|
229
|
+
rate = body.readUInt32LE(4);
|
|
230
|
+
bits = body.readUInt16LE(14);
|
|
231
|
+
if (audioFormat === FORMAT_EXTENSIBLE && body.length >= 26) {
|
|
232
|
+
// The real format is the first two bytes of the sub-format GUID.
|
|
233
|
+
audioFormat = body.readUInt16LE(24);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (chunkId === "data") {
|
|
237
|
+
payload = body;
|
|
238
|
+
}
|
|
239
|
+
// Chunks are word-aligned, and an odd size carries a pad byte.
|
|
240
|
+
at += 8 + size + (size & 1);
|
|
241
|
+
}
|
|
242
|
+
if (channels === 0 || rate === 0)
|
|
243
|
+
throw new Error("the WAV has no readable fmt chunk");
|
|
244
|
+
if (payload === undefined)
|
|
245
|
+
throw new Error("the WAV has no data chunk");
|
|
246
|
+
let samples;
|
|
247
|
+
if (audioFormat === FORMAT_FLOAT && bits === 32) {
|
|
248
|
+
samples = float32ToPcm16(payload);
|
|
249
|
+
}
|
|
250
|
+
else if (audioFormat === FORMAT_PCM && bits === 16) {
|
|
251
|
+
samples = payload;
|
|
252
|
+
}
|
|
253
|
+
else if (audioFormat === FORMAT_PCM && bits === 8) {
|
|
254
|
+
// 8-bit WAV is UNSIGNED, centred on 128. Read as signed it is a square wave
|
|
255
|
+
// of noise.
|
|
256
|
+
samples = Buffer.alloc(payload.length * 2);
|
|
257
|
+
for (let i = 0; i < payload.length; i += 1)
|
|
258
|
+
samples.writeInt16LE((payload[i] - 128) * 256, i * 2);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
throw new Error(`unsupported WAV format ${audioFormat} at ${bits} bits`);
|
|
262
|
+
}
|
|
263
|
+
if (channels > 1)
|
|
264
|
+
samples = downmix(samples, channels);
|
|
265
|
+
if (rate !== SAMPLE_RATE_HZ)
|
|
266
|
+
samples = resamplePcm16(samples, rate, SAMPLE_RATE_HZ);
|
|
267
|
+
return samples;
|
|
268
|
+
}
|
|
269
|
+
/** Wrap PCM16 mono in a WAV, for a service that will not take raw PCM. */
|
|
270
|
+
export function encodeWav(pcm, sampleRateHz = SAMPLE_RATE_HZ) {
|
|
271
|
+
const header = Buffer.alloc(44);
|
|
272
|
+
header.write("RIFF", 0, "ascii");
|
|
273
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
274
|
+
header.write("WAVE", 8, "ascii");
|
|
275
|
+
header.write("fmt ", 12, "ascii");
|
|
276
|
+
header.writeUInt32LE(16, 16);
|
|
277
|
+
header.writeUInt16LE(FORMAT_PCM, 20);
|
|
278
|
+
header.writeUInt16LE(1, 22);
|
|
279
|
+
header.writeUInt32LE(sampleRateHz, 24);
|
|
280
|
+
header.writeUInt32LE(sampleRateHz * 2, 28);
|
|
281
|
+
header.writeUInt16LE(2, 32);
|
|
282
|
+
header.writeUInt16LE(16, 34);
|
|
283
|
+
header.write("data", 36, "ascii");
|
|
284
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
285
|
+
return Buffer.concat([header, pcm]);
|
|
286
|
+
}
|
|
287
|
+
function float32ToPcm16(payload) {
|
|
288
|
+
const count = Math.floor(payload.length / 4);
|
|
289
|
+
const out = Buffer.alloc(count * 2);
|
|
290
|
+
for (let i = 0; i < count; i += 1) {
|
|
291
|
+
const value = payload.readFloatLE(i * 4);
|
|
292
|
+
// Clamped, because a float WAV is allowed outside -1..1 and wrapping there
|
|
293
|
+
// is the loudest possible click.
|
|
294
|
+
const clamped = value < -1 ? -1 : value > 1 ? 1 : value;
|
|
295
|
+
out.writeInt16LE(Math.trunc(clamped * 32767), i * 2);
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
/** Average the channels. Taking only the left loses whoever is on the right. */
|
|
300
|
+
function downmix(pcm, channels) {
|
|
301
|
+
const frame = channels * 2;
|
|
302
|
+
const count = Math.floor(pcm.length / frame);
|
|
303
|
+
const out = Buffer.alloc(count * 2);
|
|
304
|
+
for (let i = 0; i < count; i += 1) {
|
|
305
|
+
let total = 0;
|
|
306
|
+
for (let c = 0; c < channels; c += 1)
|
|
307
|
+
total += pcm.readInt16LE(i * frame + c * 2);
|
|
308
|
+
out.writeInt16LE(Math.trunc(total / channels), i * 2);
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Feed a finished buffer out at the rate a call consumes it.
|
|
314
|
+
*
|
|
315
|
+
* A text-to-speech engine returns a whole utterance at once. A call takes 20
|
|
316
|
+
* milliseconds every 20 milliseconds. Sending the buffer in one go hands the
|
|
317
|
+
* service seconds of audio it must queue, and the queue is what makes a barge-in
|
|
318
|
+
* arrive too late to matter: the caller interrupts, the model stops, and the bot
|
|
319
|
+
* keeps talking for the length of what was already sent.
|
|
320
|
+
*
|
|
321
|
+
* So it goes out paced, and the pacing is on an ABSOLUTE clock. Sleeping 20 ms
|
|
322
|
+
* per frame accumulates every scheduling delay, and a minute of speech ends
|
|
323
|
+
* seconds behind where it should be; this one waits until the next frame is DUE,
|
|
324
|
+
* so a late frame is followed by a short wait rather than a full one.
|
|
325
|
+
*
|
|
326
|
+
* ```ts
|
|
327
|
+
* const playback = new PacedPlayback((pcm) => session.sendAudio(pcm));
|
|
328
|
+
* const result = await playback.say(pcm);
|
|
329
|
+
* if (result.interrupted) { ... }
|
|
330
|
+
* ```
|
|
331
|
+
*
|
|
332
|
+
* {@link cancel} stops whatever is playing. The result says how much was heard,
|
|
333
|
+
* which is the difference between "I told them" and "I started to".
|
|
334
|
+
*/
|
|
335
|
+
export class PacedPlayback {
|
|
336
|
+
#send;
|
|
337
|
+
#frameMs;
|
|
338
|
+
#frameBytes;
|
|
339
|
+
#cancelled = false;
|
|
340
|
+
#playing = false;
|
|
341
|
+
// Serialised, so two turns cannot interleave into one stream of audio the
|
|
342
|
+
// caller hears as both at once.
|
|
343
|
+
#queue = Promise.resolve();
|
|
344
|
+
constructor(send, frameMs = FRAME_MS) {
|
|
345
|
+
this.#send = send;
|
|
346
|
+
this.#frameMs = Math.max(1, frameMs);
|
|
347
|
+
this.#frameBytes = (FRAME_BYTES * this.#frameMs) / FRAME_MS;
|
|
348
|
+
}
|
|
349
|
+
get playing() {
|
|
350
|
+
return this.#playing;
|
|
351
|
+
}
|
|
352
|
+
/** Stop what is playing. Safe from anywhere, including a receive loop. */
|
|
353
|
+
cancel() {
|
|
354
|
+
this.#cancelled = true;
|
|
355
|
+
}
|
|
356
|
+
/** Play one buffer and return what the caller actually heard. */
|
|
357
|
+
async say(pcm) {
|
|
358
|
+
const totalMs = Math.floor((Math.floor(pcm.length / 2) * 1000) / SAMPLE_RATE_HZ);
|
|
359
|
+
if (pcm.length === 0)
|
|
360
|
+
return { sentMs: 0, totalMs: 0, interrupted: false };
|
|
361
|
+
const run = this.#queue.then(() => this.#play(pcm, totalMs));
|
|
362
|
+
// Swallowed on the QUEUE only: the caller still sees the rejection through
|
|
363
|
+
// their own await, but one failed turn must not poison every later one.
|
|
364
|
+
this.#queue = run.catch(() => undefined);
|
|
365
|
+
return run;
|
|
366
|
+
}
|
|
367
|
+
async #play(pcm, totalMs) {
|
|
368
|
+
this.#cancelled = false;
|
|
369
|
+
this.#playing = true;
|
|
370
|
+
let sent = 0;
|
|
371
|
+
// The clock is absolute. Waiting a fixed step per frame accumulates every
|
|
372
|
+
// delay, and a long utterance finishes seconds late.
|
|
373
|
+
let due = Date.now();
|
|
374
|
+
try {
|
|
375
|
+
for (let at = 0; at < pcm.length; at += this.#frameBytes) {
|
|
376
|
+
if (this.#cancelled)
|
|
377
|
+
break;
|
|
378
|
+
const frame = pcm.subarray(at, at + this.#frameBytes);
|
|
379
|
+
await this.#send(frame);
|
|
380
|
+
sent += frame.length;
|
|
381
|
+
due += this.#frameMs;
|
|
382
|
+
const delay = due - Date.now();
|
|
383
|
+
if (delay > 0) {
|
|
384
|
+
await new Promise((resolve) => {
|
|
385
|
+
const timer = setTimeout(resolve, delay);
|
|
386
|
+
timer.unref?.();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
this.#playing = false;
|
|
393
|
+
}
|
|
394
|
+
const interrupted = this.#cancelled;
|
|
395
|
+
this.#cancelled = false;
|
|
396
|
+
return {
|
|
397
|
+
sentMs: Math.floor((Math.floor(sent / 2) * 1000) / SAMPLE_RATE_HZ),
|
|
398
|
+
totalMs,
|
|
399
|
+
interrupted,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
//# sourceMappingURL=voice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"voice.js","sourceRoot":"","sources":["../src/voice.ts"],"names":[],"mappings":"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"}
|