@ozaiya/openclaw-channel 0.8.0 → 0.8.2

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.
@@ -0,0 +1,360 @@
1
+ /**
2
+ * PhoneCallSession — Manages a bot's outbound phone call via LiveKit SIP.
3
+ *
4
+ * Two modes:
5
+ * - **auto**: Phone audio → STT → agent dispatch → TTS → phone audio
6
+ * - **manual**: Phone audio → STT → post to chat, user reply → TTS → phone audio
7
+ *
8
+ * Reuses STT/TTS patterns from VoiceCallSession.
9
+ */
10
+ import { Room, RoomEvent, LocalAudioTrack, AudioSource, AudioStream, AudioFrame, TrackKind, IceTransportType, TrackPublishOptions, TrackSource, } from "@livekit/rtc-node";
11
+ import { createClient, LiveTranscriptionEvents } from "@deepgram/sdk";
12
+ import { synthesizeSpeechForCall } from "./ttsEngine.js";
13
+ export class PhoneCallSession {
14
+ phoneCallId;
15
+ groupId;
16
+ mode;
17
+ room;
18
+ config;
19
+ livekitToken;
20
+ livekitUrl;
21
+ onTranscript;
22
+ onPhoneHangUp;
23
+ log;
24
+ audioSource = null;
25
+ localAudioTrack = null;
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ deepgramConnection = null;
28
+ keepAliveInterval = null;
29
+ silenceTimer = null;
30
+ connected = false;
31
+ disposed = false;
32
+ currentTtsAbort = null;
33
+ // STT reconnection state
34
+ sttReconnectAttempts = 0;
35
+ sttReconnectTimer = null;
36
+ static MAX_STT_RECONNECT = 10;
37
+ /** Accumulated transcript for reporting to server */
38
+ transcript = [];
39
+ constructor(options) {
40
+ this.phoneCallId = options.phoneCallId;
41
+ this.groupId = options.groupId;
42
+ this.mode = options.mode;
43
+ this.livekitToken = options.livekitToken;
44
+ this.livekitUrl = options.livekitUrl;
45
+ this.config = resolvePhoneCallConfig(options.voiceCallConfig);
46
+ this.onTranscript = options.onTranscript;
47
+ this.onPhoneHangUp = options.onPhoneHangUp;
48
+ this.log = options.log;
49
+ this.room = new Room();
50
+ }
51
+ /**
52
+ * Connect to the LiveKit room, publish audio track, subscribe to phone audio,
53
+ * and start the STT pipeline.
54
+ */
55
+ async connect() {
56
+ if (this.disposed)
57
+ return;
58
+ if (!this.config.deepgramApiKey) {
59
+ throw new Error("PhoneCallSession: deepgramApiKey is required");
60
+ }
61
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] connecting to LiveKit room`);
62
+ // Create AudioSource for TTS output (48kHz mono)
63
+ this.audioSource = new AudioSource(48000, 1);
64
+ this.localAudioTrack = LocalAudioTrack.createAudioTrack("bot-voice", this.audioSource);
65
+ // Listen for remote tracks (phone person's audio)
66
+ this.room.on(RoomEvent.TrackSubscribed, (track, pub, participant) => {
67
+ if (track.kind === TrackKind.KIND_AUDIO) {
68
+ this.startAudioPipeline(track);
69
+ }
70
+ });
71
+ // Detect phone hang-up (SIP participant leaves)
72
+ this.room.on(RoomEvent.ParticipantDisconnected, (participant) => {
73
+ if (participant.identity === "phone") {
74
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] phone participant disconnected (hang up)`);
75
+ this.onPhoneHangUp?.();
76
+ }
77
+ });
78
+ // Connect to LiveKit room
79
+ await this.room.connect(this.livekitUrl, this.livekitToken, {
80
+ autoSubscribe: true,
81
+ dynacast: true,
82
+ ...(this.config.forceTurnRelay ? {
83
+ rtcConfig: {
84
+ iceTransportType: IceTransportType.TRANSPORT_RELAY,
85
+ continualGatheringPolicy: 0,
86
+ iceServers: [],
87
+ },
88
+ } : {}),
89
+ });
90
+ this.connected = true;
91
+ // Publish audio track
92
+ if (this.room.localParticipant) {
93
+ await this.room.localParticipant.publishTrack(this.localAudioTrack, new TrackPublishOptions({
94
+ source: TrackSource.SOURCE_MICROPHONE,
95
+ }));
96
+ }
97
+ // Connect STT
98
+ this.connectStt();
99
+ // Subscribe to existing remote audio tracks
100
+ for (const [, participant] of this.room.remoteParticipants) {
101
+ for (const [, publication] of participant.trackPublications) {
102
+ if (publication.track && publication.kind === TrackKind.KIND_AUDIO) {
103
+ this.startAudioPipeline(publication.track);
104
+ }
105
+ }
106
+ }
107
+ this.resetSilenceTimer();
108
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] connected and publishing audio`);
109
+ }
110
+ /**
111
+ * Disconnect from LiveKit and clean up resources.
112
+ */
113
+ async disconnect() {
114
+ if (this.disposed)
115
+ return;
116
+ this.disposed = true;
117
+ this.connected = false;
118
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] disconnecting`);
119
+ this.cancelCurrentPlayback();
120
+ if (this.sttReconnectTimer) {
121
+ clearTimeout(this.sttReconnectTimer);
122
+ this.sttReconnectTimer = null;
123
+ }
124
+ if (this.silenceTimer) {
125
+ clearTimeout(this.silenceTimer);
126
+ this.silenceTimer = null;
127
+ }
128
+ if (this.keepAliveInterval) {
129
+ clearInterval(this.keepAliveInterval);
130
+ this.keepAliveInterval = null;
131
+ }
132
+ if (this.deepgramConnection) {
133
+ try {
134
+ this.deepgramConnection.requestClose();
135
+ }
136
+ catch { /* ignore */ }
137
+ this.deepgramConnection = null;
138
+ }
139
+ if (this.localAudioTrack) {
140
+ try {
141
+ await this.localAudioTrack.close(true);
142
+ }
143
+ catch { /* ignore */ }
144
+ this.localAudioTrack = null;
145
+ this.audioSource = null;
146
+ }
147
+ try {
148
+ await this.room.disconnect();
149
+ }
150
+ catch { /* ignore */ }
151
+ }
152
+ /**
153
+ * Synthesize text to speech and play through the phone call audio track.
154
+ * Uses unified TTS engine (Deepgram or Volcengine) based on config/language.
155
+ */
156
+ async speakText(text, abortSignal) {
157
+ if (this.disposed || !this.audioSource)
158
+ return;
159
+ // Record in transcript
160
+ this.transcript.push({ speaker: "bot", text, ts: Date.now() });
161
+ const pcmData = await synthesizeSpeechForCall(text, this.config.callTts, abortSignal, this.log);
162
+ if (!pcmData || abortSignal?.aborted || this.disposed)
163
+ return;
164
+ const samplesPerFrame = 960; // 20ms at 48kHz
165
+ for (let i = 0; i < pcmData.length; i += samplesPerFrame) {
166
+ if (abortSignal?.aborted || this.disposed)
167
+ return;
168
+ const end = Math.min(i + samplesPerFrame, pcmData.length);
169
+ const frameData = pcmData.slice(i, end);
170
+ const frame = new AudioFrame(frameData, 48000, 1, frameData.length);
171
+ await this.audioSource.captureFrame(frame);
172
+ }
173
+ }
174
+ /**
175
+ * Speak a full reply with sentence-level streaming and barge-in support.
176
+ */
177
+ async speakReply(text) {
178
+ if (this.disposed)
179
+ return;
180
+ const abort = new AbortController();
181
+ this.currentTtsAbort = abort;
182
+ try {
183
+ const sentences = splitIntoSentences(text);
184
+ for (const sentence of sentences) {
185
+ if (abort.signal.aborted || this.disposed)
186
+ return;
187
+ await this.speakText(sentence, abort.signal);
188
+ }
189
+ }
190
+ catch (err) {
191
+ if (abort.signal.aborted)
192
+ return;
193
+ throw err;
194
+ }
195
+ finally {
196
+ if (this.currentTtsAbort === abort) {
197
+ this.currentTtsAbort = null;
198
+ }
199
+ }
200
+ }
201
+ cancelCurrentPlayback() {
202
+ if (this.currentTtsAbort) {
203
+ this.currentTtsAbort.abort();
204
+ this.currentTtsAbort = null;
205
+ }
206
+ this.audioSource?.clearQueue();
207
+ }
208
+ get isDisposed() {
209
+ return this.disposed;
210
+ }
211
+ // --- Private ---
212
+ connectStt() {
213
+ const deepgram = createClient(this.config.deepgramApiKey);
214
+ const connection = deepgram.listen.live({
215
+ model: this.config.sttModel,
216
+ language: this.config.sttLanguage,
217
+ encoding: "linear16",
218
+ sample_rate: 16000,
219
+ channels: 1,
220
+ interim_results: true,
221
+ utterance_end_ms: this.config.utteranceEndMs,
222
+ endpointing: this.config.endpointing,
223
+ vad_events: true,
224
+ smart_format: true,
225
+ });
226
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
227
+ connection.on(LiveTranscriptionEvents.Transcript, (data) => {
228
+ const transcript = data.channel?.alternatives?.[0]?.transcript;
229
+ if (transcript && transcript.trim().length > 0) {
230
+ const isFinal = data.is_final ?? false;
231
+ if (isFinal) {
232
+ this.sttReconnectAttempts = 0;
233
+ this.resetSilenceTimer();
234
+ const text = transcript.trim();
235
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] STT final: "${text}"`);
236
+ this.transcript.push({ speaker: "phone", text, ts: Date.now() });
237
+ this.onTranscript(text);
238
+ }
239
+ }
240
+ });
241
+ connection.on(LiveTranscriptionEvents.SpeechStarted, () => {
242
+ this.cancelCurrentPlayback();
243
+ });
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ connection.on(LiveTranscriptionEvents.Error, (err) => {
246
+ this.log?.warn?.(`[phone-call:${this.phoneCallId}] Deepgram STT error: ${err instanceof Error ? err.message : JSON.stringify(err)}`);
247
+ });
248
+ connection.on(LiveTranscriptionEvents.Close, () => {
249
+ if (!this.disposed) {
250
+ this.log?.warn?.(`[phone-call:${this.phoneCallId}] Deepgram STT closed unexpectedly, scheduling reconnect`);
251
+ this.scheduleReconnectStt();
252
+ }
253
+ });
254
+ this.deepgramConnection = connection;
255
+ if (this.keepAliveInterval)
256
+ clearInterval(this.keepAliveInterval);
257
+ this.keepAliveInterval = setInterval(() => {
258
+ try {
259
+ this.deepgramConnection?.keepAlive();
260
+ }
261
+ catch { /* ignore */ }
262
+ }, 5000);
263
+ }
264
+ scheduleReconnectStt() {
265
+ if (this.disposed || this.sttReconnectAttempts >= PhoneCallSession.MAX_STT_RECONNECT) {
266
+ this.log?.warn?.(`[phone-call:${this.phoneCallId}] STT max reconnect attempts reached`);
267
+ return;
268
+ }
269
+ const delay = Math.min(1000 * Math.pow(2, this.sttReconnectAttempts), 30000);
270
+ this.sttReconnectAttempts++;
271
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] STT reconnect attempt ${this.sttReconnectAttempts} in ${delay}ms`);
272
+ this.sttReconnectTimer = setTimeout(() => {
273
+ if (this.disposed)
274
+ return;
275
+ if (this.keepAliveInterval) {
276
+ clearInterval(this.keepAliveInterval);
277
+ this.keepAliveInterval = null;
278
+ }
279
+ try {
280
+ this.deepgramConnection?.requestClose();
281
+ }
282
+ catch { /* ignore */ }
283
+ this.deepgramConnection = null;
284
+ this.connectStt();
285
+ for (const [, participant] of this.room.remoteParticipants) {
286
+ for (const [, publication] of participant.trackPublications) {
287
+ if (publication.track && publication.kind === TrackKind.KIND_AUDIO) {
288
+ this.startAudioPipeline(publication.track);
289
+ }
290
+ }
291
+ }
292
+ }, delay);
293
+ }
294
+ startAudioPipeline(track) {
295
+ if (this.disposed || !this.deepgramConnection)
296
+ return;
297
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] subscribing to phone audio`);
298
+ const audioStream = new AudioStream(track, { sampleRate: 16000, numChannels: 1 });
299
+ const dgConn = this.deepgramConnection;
300
+ void (async () => {
301
+ for await (const frame of audioStream) {
302
+ if (this.disposed || !this.deepgramConnection)
303
+ break;
304
+ try {
305
+ const data = frame.data;
306
+ const arrayBuffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
307
+ dgConn.send(arrayBuffer);
308
+ }
309
+ catch {
310
+ break;
311
+ }
312
+ }
313
+ })();
314
+ }
315
+ resetSilenceTimer() {
316
+ if (this.silenceTimer)
317
+ clearTimeout(this.silenceTimer);
318
+ this.silenceTimer = setTimeout(() => {
319
+ this.log?.info?.(`[phone-call:${this.phoneCallId}] silence timeout (${this.config.silenceTimeoutSeconds}s), disconnecting`);
320
+ this.onPhoneHangUp?.();
321
+ }, this.config.silenceTimeoutSeconds * 1000);
322
+ }
323
+ }
324
+ function resolvePhoneCallConfig(cfg) {
325
+ const deepgramApiKey = cfg.deepgramApiKey || process.env.DEEPGRAM_API_KEY || "";
326
+ const sttLanguage = cfg.stt?.language ?? "en";
327
+ const ttsModel = cfg.tts?.model ?? "aura-asteria-en";
328
+ return {
329
+ deepgramApiKey,
330
+ sttModel: cfg.stt?.model ?? "nova-2",
331
+ sttLanguage,
332
+ utteranceEndMs: cfg.stt?.utteranceEndMs ?? 500,
333
+ endpointing: cfg.stt?.endpointing ?? 300,
334
+ ttsModel,
335
+ silenceTimeoutSeconds: cfg.silenceTimeoutSeconds ?? 300, // 5min for phone calls
336
+ forceTurnRelay: cfg.forceTurnRelay ?? false,
337
+ callTts: {
338
+ ttsEngine: cfg.ttsEngine ?? "auto",
339
+ sttLanguage,
340
+ deepgramApiKey,
341
+ deepgramTtsModel: ttsModel,
342
+ volcengineTts: cfg.volcengineTts,
343
+ },
344
+ };
345
+ }
346
+ function splitIntoSentences(text) {
347
+ const sentences = [];
348
+ const regex = /[^.!?]*[.!?]+[\s]*/g;
349
+ let match;
350
+ let lastIndex = 0;
351
+ while ((match = regex.exec(text)) !== null) {
352
+ sentences.push(match[0].trim());
353
+ lastIndex = regex.lastIndex;
354
+ }
355
+ const remainder = text.slice(lastIndex).trim();
356
+ if (remainder)
357
+ sentences.push(remainder);
358
+ return sentences.length > 0 ? sentences : [text];
359
+ }
360
+ //# sourceMappingURL=phoneCall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phoneCall.js","sourceRoot":"","sources":["../../src/phoneCall.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Unified TTS engine for voice/phone calls.
3
+ *
4
+ * Supports two backends:
5
+ * - Deepgram Aura: returns linear16 PCM at 48kHz (English)
6
+ * - Volcengine: returns raw PCM at 48kHz (Chinese + other languages)
7
+ *
8
+ * Auto-select: if stt.language starts with "zh", use volcengine; else deepgram.
9
+ */
10
+ import type { OzaiyaVolcengineTtsConfig } from "./types.js";
11
+ export type TtsEngineType = "deepgram" | "volcengine" | "auto";
12
+ export interface CallTtsConfig {
13
+ ttsEngine: TtsEngineType;
14
+ sttLanguage: string;
15
+ deepgramApiKey: string;
16
+ deepgramTtsModel: string;
17
+ volcengineTts?: OzaiyaVolcengineTtsConfig;
18
+ }
19
+ interface TtsLog {
20
+ info?: (msg: string) => void;
21
+ warn?: (msg: string) => void;
22
+ }
23
+ /**
24
+ * Resolve which TTS engine to use based on config and language.
25
+ */
26
+ export declare function resolveEngine(config: CallTtsConfig): "deepgram" | "volcengine";
27
+ /**
28
+ * Synthesize speech for a voice/phone call.
29
+ * Returns Int16Array of PCM audio at 48kHz mono, ready for LiveKit AudioFrame.
30
+ */
31
+ export declare function synthesizeSpeechForCall(text: string, config: CallTtsConfig, abortSignal?: AbortSignal, log?: TtsLog): Promise<Int16Array | null>;
32
+ export {};
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Resolve which TTS engine to use based on config and language.
3
+ */
4
+ export function resolveEngine(config) {
5
+ if (config.ttsEngine === "deepgram")
6
+ return "deepgram";
7
+ if (config.ttsEngine === "volcengine")
8
+ return "volcengine";
9
+ // auto: pick by language
10
+ if (config.sttLanguage.startsWith("zh"))
11
+ return "volcengine";
12
+ return "deepgram";
13
+ }
14
+ /**
15
+ * Synthesize speech for a voice/phone call.
16
+ * Returns Int16Array of PCM audio at 48kHz mono, ready for LiveKit AudioFrame.
17
+ */
18
+ export async function synthesizeSpeechForCall(text, config, abortSignal, log) {
19
+ const engine = resolveEngine(config);
20
+ if (engine === "volcengine") {
21
+ if (!config.volcengineTts) {
22
+ log?.warn?.("ttsEngine: volcengine selected but no volcengineTts config");
23
+ // Fallback to deepgram
24
+ return synthesizeDeepgram(text, config.deepgramApiKey, config.deepgramTtsModel, abortSignal, log);
25
+ }
26
+ return synthesizeVolcengineCall(text, config.volcengineTts, abortSignal, log);
27
+ }
28
+ return synthesizeDeepgram(text, config.deepgramApiKey, config.deepgramTtsModel, abortSignal, log);
29
+ }
30
+ /**
31
+ * Deepgram Aura TTS → PCM Int16Array at 48kHz.
32
+ */
33
+ async function synthesizeDeepgram(text, apiKey, model, abortSignal, log) {
34
+ const url = `https://api.deepgram.com/v1/speak?model=${encodeURIComponent(model)}&encoding=linear16&sample_rate=48000`;
35
+ const res = await fetch(url, {
36
+ method: "POST",
37
+ headers: {
38
+ Authorization: `Token ${apiKey}`,
39
+ "Content-Type": "application/json",
40
+ },
41
+ body: JSON.stringify({ text }),
42
+ signal: abortSignal,
43
+ });
44
+ if (!res.ok) {
45
+ log?.warn?.(`ttsEngine: Deepgram TTS error: ${res.status} ${res.statusText}`);
46
+ return null;
47
+ }
48
+ const arrayBuffer = await res.arrayBuffer();
49
+ if (abortSignal?.aborted)
50
+ return null;
51
+ return new Int16Array(arrayBuffer);
52
+ }
53
+ /**
54
+ * Volcengine TTS → PCM Int16Array at 48kHz.
55
+ * Priority: arkApiKey → apiKey (v3) → accessToken (v1).
56
+ * Requests raw PCM encoding at 48kHz for direct LiveKit playback.
57
+ */
58
+ async function synthesizeVolcengineCall(text, config, abortSignal, log) {
59
+ const voice = config.voice ?? "zh_female_wanwanxiaohe_moon_bigtts";
60
+ const speedRatio = config.speedRatio ?? 1.0;
61
+ try {
62
+ if (config.arkApiKey) {
63
+ return await volcGatewayPcm(text, config.arkApiKey, voice, speedRatio, abortSignal, log);
64
+ }
65
+ else if (config.apiKey) {
66
+ const resourceId = config.resourceId ?? "volc.service_type.10029";
67
+ return await volcV3Pcm(text, config.apiKey, voice, speedRatio, resourceId, abortSignal, log);
68
+ }
69
+ else if (config.accessToken) {
70
+ return await volcV1Pcm(text, config, voice, speedRatio, abortSignal, log);
71
+ }
72
+ else {
73
+ log?.warn?.("ttsEngine: Volcengine: no auth configured");
74
+ return null;
75
+ }
76
+ }
77
+ catch (err) {
78
+ log?.warn?.(`ttsEngine: Volcengine TTS failed: ${String(err)}`);
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Volcengine AI Gateway → raw PCM 48kHz.
84
+ */
85
+ async function volcGatewayPcm(text, arkApiKey, voice, speedRatio, abortSignal, log) {
86
+ log?.info?.(`ttsEngine: Volcengine Gateway PCM: voice=${voice}`);
87
+ const res = await fetch("https://ai-gateway.vei.volces.com/v1/audio/speech", {
88
+ method: "POST",
89
+ headers: {
90
+ "Content-Type": "application/json",
91
+ Authorization: `Bearer ${arkApiKey}`,
92
+ },
93
+ body: JSON.stringify({
94
+ model: "doubao-tts",
95
+ input: text,
96
+ voice,
97
+ response_format: "pcm",
98
+ speed: speedRatio,
99
+ }),
100
+ signal: abortSignal,
101
+ });
102
+ if (!res.ok) {
103
+ const errText = await res.text().catch(() => "");
104
+ log?.warn?.(`ttsEngine: Volcengine Gateway PCM error: ${res.status} ${errText}`);
105
+ return null;
106
+ }
107
+ const buf = await res.arrayBuffer();
108
+ if (buf.byteLength === 0)
109
+ return null;
110
+ // Gateway returns 24kHz PCM by default — upsample to 48kHz
111
+ const src = new Int16Array(buf);
112
+ return upsample24kTo48k(src);
113
+ }
114
+ /**
115
+ * Volcengine v3 streaming → raw PCM 48kHz.
116
+ */
117
+ async function volcV3Pcm(text, apiKey, voice, speedRatio, resourceId, abortSignal, log) {
118
+ const headers = {
119
+ "Content-Type": "application/json",
120
+ "X-Api-Key": apiKey,
121
+ "X-Api-Resource-Id": resourceId,
122
+ "X-Api-Connect-Id": `tts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
123
+ };
124
+ const body = {
125
+ user: { uid: "ozaiya-bot" },
126
+ req_params: { text, speaker: voice, speed_ratio: speedRatio },
127
+ audio_params: { format: "pcm", sample_rate: 48000 },
128
+ };
129
+ const res = await fetch("https://openspeech.bytedance.com/api/v3/tts/unidirectional", {
130
+ method: "POST",
131
+ headers,
132
+ body: JSON.stringify(body),
133
+ signal: abortSignal,
134
+ });
135
+ if (!res.ok) {
136
+ log?.warn?.(`ttsEngine: Volcengine v3 PCM error: ${res.status}`);
137
+ return null;
138
+ }
139
+ const responseText = await res.text();
140
+ const chunks = [];
141
+ for (const line of responseText.split("\n")) {
142
+ const trimmed = line.trim();
143
+ if (!trimmed)
144
+ continue;
145
+ try {
146
+ const chunk = JSON.parse(trimmed);
147
+ if (chunk.data)
148
+ chunks.push(Buffer.from(chunk.data, "base64"));
149
+ }
150
+ catch { /* skip */ }
151
+ }
152
+ if (chunks.length === 0)
153
+ return null;
154
+ const combined = Buffer.concat(chunks);
155
+ return new Int16Array(combined.buffer, combined.byteOffset, combined.byteLength / 2);
156
+ }
157
+ /**
158
+ * Volcengine v1 → raw PCM, upsample to 48kHz.
159
+ */
160
+ async function volcV1Pcm(text, config, voice, speedRatio, abortSignal, log) {
161
+ const cluster = config.cluster ?? "volcano_tts";
162
+ const payload = {
163
+ app: { appid: config.appId ?? "", token: config.accessToken ?? "", cluster },
164
+ user: { uid: "ozaiya-bot" },
165
+ audio: { voice_type: voice, encoding: "pcm", speed_ratio: speedRatio, rate: 24000 },
166
+ request: { reqid: `tts-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, text, operation: "query" },
167
+ };
168
+ const res = await fetch("https://openspeech.bytedance.com/api/v1/tts", {
169
+ method: "POST",
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ Authorization: `Bearer;${config.accessToken}`,
173
+ },
174
+ body: JSON.stringify(payload),
175
+ signal: abortSignal,
176
+ });
177
+ if (!res.ok) {
178
+ log?.warn?.(`ttsEngine: Volcengine v1 PCM error: ${res.status}`);
179
+ return null;
180
+ }
181
+ const result = (await res.json());
182
+ if (result.code !== 3000 || !result.data)
183
+ return null;
184
+ const buf = Buffer.from(result.data, "base64");
185
+ const src = new Int16Array(buf.buffer, buf.byteOffset, buf.byteLength / 2);
186
+ // v1 returns 24kHz — upsample to 48kHz
187
+ return upsample24kTo48k(src);
188
+ }
189
+ /**
190
+ * Simple linear interpolation upsample from 24kHz to 48kHz (2x).
191
+ */
192
+ function upsample24kTo48k(src) {
193
+ const dst = new Int16Array(src.length * 2);
194
+ for (let i = 0; i < src.length; i++) {
195
+ dst[i * 2] = src[i];
196
+ // Interpolate: average of current and next sample (clamp at end)
197
+ const next = i + 1 < src.length ? src[i + 1] : src[i];
198
+ dst[i * 2 + 1] = Math.round((src[i] + next) / 2);
199
+ }
200
+ return dst;
201
+ }
202
+ //# sourceMappingURL=ttsEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ttsEngine.js","sourceRoot":"","sources":["../../src/ttsEngine.ts"],"names":[],"mappings":"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"}