@drawdream/livespeech 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 +156 -0
- package/dist/index.d.mts +555 -0
- package/dist/index.d.ts +555 -0
- package/dist/index.js +978 -0
- package/dist/index.mjs +938 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AudioEncoder: () => AudioEncoder,
|
|
24
|
+
LiveSpeechClient: () => LiveSpeechClient,
|
|
25
|
+
Region: () => Region,
|
|
26
|
+
createWavHeader: () => createWavHeader,
|
|
27
|
+
decodeBase64ToAudio: () => decodeBase64ToAudio,
|
|
28
|
+
encodeAudioToBase64: () => encodeAudioToBase64,
|
|
29
|
+
extractPcmFromWav: () => extractPcmFromWav,
|
|
30
|
+
float32ToInt16: () => float32ToInt16,
|
|
31
|
+
getEndpointForRegion: () => getEndpointForRegion,
|
|
32
|
+
int16ToFloat32: () => int16ToFloat32,
|
|
33
|
+
int16ToUint8: () => int16ToUint8,
|
|
34
|
+
isValidRegion: () => isValidRegion,
|
|
35
|
+
uint8ToInt16: () => uint8ToInt16,
|
|
36
|
+
wrapPcmInWav: () => wrapPcmInWav
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/types/regions.ts
|
|
41
|
+
var Region = {
|
|
42
|
+
/** Asia Pacific (Seoul) */
|
|
43
|
+
AP_NORTHEAST_2: "ap-northeast-2",
|
|
44
|
+
/** US West (Oregon) - Coming soon */
|
|
45
|
+
US_WEST_2: "us-west-2"
|
|
46
|
+
};
|
|
47
|
+
var REGION_ENDPOINTS = {
|
|
48
|
+
"ap-northeast-2": "wss://talk.drawdream.co.kr",
|
|
49
|
+
"us-west-2": "wss://talk..drawdream.ca"
|
|
50
|
+
// Coming soon
|
|
51
|
+
};
|
|
52
|
+
function getEndpointForRegion(region) {
|
|
53
|
+
const endpoint = REGION_ENDPOINTS[region];
|
|
54
|
+
if (!endpoint) {
|
|
55
|
+
throw new Error(`Unknown region: ${region}. Available regions: ${Object.keys(REGION_ENDPOINTS).join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
return endpoint;
|
|
58
|
+
}
|
|
59
|
+
function isValidRegion(value) {
|
|
60
|
+
return value in REGION_ENDPOINTS;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/types/messages.ts
|
|
64
|
+
function isServerMessage(data) {
|
|
65
|
+
return typeof data === "object" && data !== null && "type" in data && typeof data.type === "string";
|
|
66
|
+
}
|
|
67
|
+
function parseServerMessage(json) {
|
|
68
|
+
try {
|
|
69
|
+
const data = JSON.parse(json);
|
|
70
|
+
if (isServerMessage(data)) {
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function serializeClientMessage(message) {
|
|
79
|
+
return JSON.stringify(message);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/utils/logger.ts
|
|
83
|
+
function createLogger(prefix, enabled) {
|
|
84
|
+
const formatMessage = (level, message) => {
|
|
85
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
return `[${timestamp}] [${prefix}] [${level.toUpperCase()}] ${message}`;
|
|
87
|
+
};
|
|
88
|
+
const noop = () => {
|
|
89
|
+
};
|
|
90
|
+
if (!enabled) {
|
|
91
|
+
return {
|
|
92
|
+
debug: noop,
|
|
93
|
+
info: noop,
|
|
94
|
+
warn: noop,
|
|
95
|
+
error: noop
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
debug(message, ...args) {
|
|
100
|
+
console.debug(formatMessage("debug", message), ...args);
|
|
101
|
+
},
|
|
102
|
+
info(message, ...args) {
|
|
103
|
+
console.info(formatMessage("info", message), ...args);
|
|
104
|
+
},
|
|
105
|
+
warn(message, ...args) {
|
|
106
|
+
console.warn(formatMessage("warn", message), ...args);
|
|
107
|
+
},
|
|
108
|
+
error(message, ...args) {
|
|
109
|
+
console.error(formatMessage("error", message), ...args);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/utils/retry.ts
|
|
115
|
+
var DEFAULT_OPTIONS = {
|
|
116
|
+
maxAttempts: 5,
|
|
117
|
+
baseDelay: 1e3,
|
|
118
|
+
maxDelay: 3e4,
|
|
119
|
+
backoffMultiplier: 2,
|
|
120
|
+
jitter: true,
|
|
121
|
+
isRetryable: () => true
|
|
122
|
+
};
|
|
123
|
+
function calculateDelay(attempt, baseDelay, maxDelay, backoffMultiplier, jitter) {
|
|
124
|
+
const exponentialDelay = baseDelay * Math.pow(backoffMultiplier, attempt - 1);
|
|
125
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelay);
|
|
126
|
+
if (jitter) {
|
|
127
|
+
const jitterRange = cappedDelay * 0.25;
|
|
128
|
+
const jitterValue = Math.random() * jitterRange * 2 - jitterRange;
|
|
129
|
+
return Math.max(0, Math.round(cappedDelay + jitterValue));
|
|
130
|
+
}
|
|
131
|
+
return Math.round(cappedDelay);
|
|
132
|
+
}
|
|
133
|
+
function sleep(ms) {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
setTimeout(resolve, ms);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
var RetryController = class {
|
|
139
|
+
attempt = 0;
|
|
140
|
+
options;
|
|
141
|
+
aborted = false;
|
|
142
|
+
constructor(options = {}) {
|
|
143
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get current attempt number
|
|
147
|
+
*/
|
|
148
|
+
get currentAttempt() {
|
|
149
|
+
return this.attempt;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if more retries are available
|
|
153
|
+
*/
|
|
154
|
+
get canRetry() {
|
|
155
|
+
return !this.aborted && this.attempt < this.options.maxAttempts;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get delay for next retry attempt
|
|
159
|
+
*/
|
|
160
|
+
getNextDelay() {
|
|
161
|
+
return calculateDelay(
|
|
162
|
+
this.attempt + 1,
|
|
163
|
+
this.options.baseDelay,
|
|
164
|
+
this.options.maxDelay,
|
|
165
|
+
this.options.backoffMultiplier,
|
|
166
|
+
this.options.jitter
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Record a retry attempt
|
|
171
|
+
* @returns The delay to wait before retrying, or null if no more retries
|
|
172
|
+
*/
|
|
173
|
+
recordAttempt(error) {
|
|
174
|
+
if (!this.canRetry) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (error && !this.options.isRetryable(error)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
this.attempt++;
|
|
181
|
+
const delay = this.getNextDelay();
|
|
182
|
+
if (this.options.onRetry && error) {
|
|
183
|
+
this.options.onRetry(this.attempt, delay, error);
|
|
184
|
+
}
|
|
185
|
+
return delay;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Reset the controller
|
|
189
|
+
*/
|
|
190
|
+
reset() {
|
|
191
|
+
this.attempt = 0;
|
|
192
|
+
this.aborted = false;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Abort any further retries
|
|
196
|
+
*/
|
|
197
|
+
abort() {
|
|
198
|
+
this.aborted = true;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/websocket/connection.ts
|
|
203
|
+
var WebSocketConnection = class {
|
|
204
|
+
ws = null;
|
|
205
|
+
state = "disconnected";
|
|
206
|
+
connectionId = null;
|
|
207
|
+
config;
|
|
208
|
+
logger;
|
|
209
|
+
events;
|
|
210
|
+
retryController;
|
|
211
|
+
pingInterval = null;
|
|
212
|
+
constructor(config, events = {}) {
|
|
213
|
+
this.config = config;
|
|
214
|
+
this.events = events;
|
|
215
|
+
this.logger = createLogger("WebSocket", config.debug);
|
|
216
|
+
this.retryController = new RetryController({
|
|
217
|
+
maxAttempts: config.maxReconnectAttempts,
|
|
218
|
+
baseDelay: config.reconnectDelay
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get current connection state
|
|
223
|
+
*/
|
|
224
|
+
get currentState() {
|
|
225
|
+
return this.state;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get connection ID
|
|
229
|
+
*/
|
|
230
|
+
get id() {
|
|
231
|
+
return this.connectionId;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Check if connected
|
|
235
|
+
*/
|
|
236
|
+
get isConnected() {
|
|
237
|
+
return this.state === "connected" && this.ws?.readyState === WebSocket.OPEN;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Connect to WebSocket server
|
|
241
|
+
*/
|
|
242
|
+
async connect() {
|
|
243
|
+
if (this.state === "connected" || this.state === "connecting") {
|
|
244
|
+
this.logger.warn("Already connected or connecting");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.state = "connecting";
|
|
248
|
+
this.retryController.reset();
|
|
249
|
+
await this.attemptConnection();
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Disconnect from WebSocket server
|
|
253
|
+
*/
|
|
254
|
+
disconnect() {
|
|
255
|
+
this.retryController.abort();
|
|
256
|
+
this.stopPingInterval();
|
|
257
|
+
if (this.ws) {
|
|
258
|
+
this.ws.close(1e3, "Client disconnect");
|
|
259
|
+
this.ws = null;
|
|
260
|
+
}
|
|
261
|
+
this.state = "disconnected";
|
|
262
|
+
this.connectionId = null;
|
|
263
|
+
this.logger.info("Disconnected");
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Send a message
|
|
267
|
+
*/
|
|
268
|
+
send(message) {
|
|
269
|
+
if (!this.isConnected) {
|
|
270
|
+
throw new Error("Not connected");
|
|
271
|
+
}
|
|
272
|
+
const data = serializeClientMessage(message);
|
|
273
|
+
this.logger.debug("Sending message:", message.action);
|
|
274
|
+
this.ws.send(data);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Attempt to establish connection
|
|
278
|
+
*/
|
|
279
|
+
async attemptConnection() {
|
|
280
|
+
return new Promise((resolve, reject) => {
|
|
281
|
+
try {
|
|
282
|
+
const url = new URL(this.config.endpoint);
|
|
283
|
+
url.searchParams.set("apiKey", this.config.apiKey);
|
|
284
|
+
this.logger.info("Connecting to", url.origin);
|
|
285
|
+
this.ws = new WebSocket(url.toString());
|
|
286
|
+
const timeoutId = setTimeout(() => {
|
|
287
|
+
if (this.state === "connecting") {
|
|
288
|
+
this.ws?.close();
|
|
289
|
+
reject(new Error("Connection timeout"));
|
|
290
|
+
}
|
|
291
|
+
}, this.config.connectionTimeout);
|
|
292
|
+
this.ws.onopen = () => {
|
|
293
|
+
clearTimeout(timeoutId);
|
|
294
|
+
this.logger.info("Connected");
|
|
295
|
+
this.state = "connected";
|
|
296
|
+
this.connectionId = this.generateConnectionId();
|
|
297
|
+
this.retryController.reset();
|
|
298
|
+
this.startPingInterval();
|
|
299
|
+
this.events.onOpen?.(this.connectionId);
|
|
300
|
+
resolve();
|
|
301
|
+
};
|
|
302
|
+
this.ws.onclose = (event) => {
|
|
303
|
+
clearTimeout(timeoutId);
|
|
304
|
+
this.handleClose(event.code, event.reason);
|
|
305
|
+
if (this.state === "connecting") {
|
|
306
|
+
reject(new Error(`Connection closed: ${event.reason || "Unknown reason"}`));
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
this.ws.onerror = (_event) => {
|
|
310
|
+
clearTimeout(timeoutId);
|
|
311
|
+
const error = new Error("WebSocket error");
|
|
312
|
+
this.logger.error("Connection error");
|
|
313
|
+
this.events.onError?.(error);
|
|
314
|
+
if (this.state === "connecting") {
|
|
315
|
+
reject(error);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
this.ws.onmessage = (event) => {
|
|
319
|
+
this.handleMessage(event.data);
|
|
320
|
+
};
|
|
321
|
+
} catch (error) {
|
|
322
|
+
reject(error);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Generate a client-side connection ID
|
|
328
|
+
*/
|
|
329
|
+
generateConnectionId() {
|
|
330
|
+
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Handle incoming message
|
|
334
|
+
*/
|
|
335
|
+
handleMessage(data, onFirstConnect) {
|
|
336
|
+
const message = parseServerMessage(data);
|
|
337
|
+
if (!message) {
|
|
338
|
+
this.logger.warn("Invalid message received:", data);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
this.logger.debug("Received message:", message.type);
|
|
342
|
+
if (message.type === "connected") {
|
|
343
|
+
this.connectionId = message.connectionId;
|
|
344
|
+
this.state = "connected";
|
|
345
|
+
this.retryController.reset();
|
|
346
|
+
this.startPingInterval();
|
|
347
|
+
this.events.onOpen?.(message.connectionId);
|
|
348
|
+
onFirstConnect?.();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (message.type === "pong") {
|
|
352
|
+
this.logger.debug("Pong received");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.events.onMessage?.(message);
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Handle connection close
|
|
359
|
+
*/
|
|
360
|
+
handleClose(code, reason) {
|
|
361
|
+
this.logger.info("Connection closed:", code, reason);
|
|
362
|
+
this.stopPingInterval();
|
|
363
|
+
const wasConnected = this.state === "connected";
|
|
364
|
+
this.ws = null;
|
|
365
|
+
if (wasConnected && this.config.autoReconnect && code !== 1e3) {
|
|
366
|
+
this.handleReconnection();
|
|
367
|
+
} else {
|
|
368
|
+
this.state = "disconnected";
|
|
369
|
+
this.connectionId = null;
|
|
370
|
+
this.events.onClose?.(code, reason);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Handle reconnection logic
|
|
375
|
+
*/
|
|
376
|
+
async handleReconnection() {
|
|
377
|
+
const delay = this.retryController.recordAttempt();
|
|
378
|
+
if (delay === null) {
|
|
379
|
+
this.logger.error("Max reconnection attempts reached");
|
|
380
|
+
this.state = "disconnected";
|
|
381
|
+
this.connectionId = null;
|
|
382
|
+
this.events.onClose?.(1006, "Max reconnection attempts reached");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
this.state = "reconnecting";
|
|
386
|
+
const attempt = this.retryController.currentAttempt;
|
|
387
|
+
const maxAttempts = this.config.maxReconnectAttempts;
|
|
388
|
+
this.logger.info(`Reconnecting in ${delay}ms (attempt ${attempt}/${maxAttempts})`);
|
|
389
|
+
this.events.onReconnecting?.(attempt, maxAttempts, delay);
|
|
390
|
+
await sleep(delay);
|
|
391
|
+
if (this.state !== "reconnecting") {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
await this.attemptConnection();
|
|
396
|
+
} catch (error) {
|
|
397
|
+
this.logger.error("Reconnection failed:", error);
|
|
398
|
+
this.handleReconnection();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Start ping interval for keep-alive
|
|
403
|
+
*/
|
|
404
|
+
startPingInterval() {
|
|
405
|
+
this.stopPingInterval();
|
|
406
|
+
this.pingInterval = setInterval(() => {
|
|
407
|
+
if (this.isConnected) {
|
|
408
|
+
this.send({ action: "ping" });
|
|
409
|
+
}
|
|
410
|
+
}, 3e4);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Stop ping interval
|
|
414
|
+
*/
|
|
415
|
+
stopPingInterval() {
|
|
416
|
+
if (this.pingInterval) {
|
|
417
|
+
clearInterval(this.pingInterval);
|
|
418
|
+
this.pingInterval = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/audio/encoder.ts
|
|
424
|
+
var DEFAULT_OPTIONS2 = {
|
|
425
|
+
format: "pcm16",
|
|
426
|
+
sampleRate: 16e3,
|
|
427
|
+
channels: 1,
|
|
428
|
+
bitDepth: 16
|
|
429
|
+
};
|
|
430
|
+
function encodeAudioToBase64(data) {
|
|
431
|
+
if (typeof Buffer !== "undefined") {
|
|
432
|
+
return Buffer.from(data).toString("base64");
|
|
433
|
+
}
|
|
434
|
+
let binary = "";
|
|
435
|
+
const len = data.byteLength;
|
|
436
|
+
for (let i = 0; i < len; i++) {
|
|
437
|
+
binary += String.fromCharCode(data[i]);
|
|
438
|
+
}
|
|
439
|
+
return typeof btoa !== "undefined" ? btoa(binary) : binary;
|
|
440
|
+
}
|
|
441
|
+
function decodeBase64ToAudio(base64) {
|
|
442
|
+
if (typeof Buffer !== "undefined") {
|
|
443
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
444
|
+
}
|
|
445
|
+
const binary = typeof atob !== "undefined" ? atob(base64) : base64;
|
|
446
|
+
const len = binary.length;
|
|
447
|
+
const bytes = new Uint8Array(len);
|
|
448
|
+
for (let i = 0; i < len; i++) {
|
|
449
|
+
bytes[i] = binary.charCodeAt(i);
|
|
450
|
+
}
|
|
451
|
+
return bytes;
|
|
452
|
+
}
|
|
453
|
+
function float32ToInt16(float32Array) {
|
|
454
|
+
const int16Array = new Int16Array(float32Array.length);
|
|
455
|
+
for (let i = 0; i < float32Array.length; i++) {
|
|
456
|
+
const sample = float32Array[i];
|
|
457
|
+
const clamped = Math.max(-1, Math.min(1, sample));
|
|
458
|
+
int16Array[i] = clamped < 0 ? clamped * 32768 : clamped * 32767;
|
|
459
|
+
}
|
|
460
|
+
return int16Array;
|
|
461
|
+
}
|
|
462
|
+
function int16ToFloat32(int16Array) {
|
|
463
|
+
const float32Array = new Float32Array(int16Array.length);
|
|
464
|
+
for (let i = 0; i < int16Array.length; i++) {
|
|
465
|
+
const sample = int16Array[i];
|
|
466
|
+
float32Array[i] = sample / (sample < 0 ? 32768 : 32767);
|
|
467
|
+
}
|
|
468
|
+
return float32Array;
|
|
469
|
+
}
|
|
470
|
+
function int16ToUint8(int16Array) {
|
|
471
|
+
const uint8Array = new Uint8Array(int16Array.length * 2);
|
|
472
|
+
const view = new DataView(uint8Array.buffer);
|
|
473
|
+
for (let i = 0; i < int16Array.length; i++) {
|
|
474
|
+
view.setInt16(i * 2, int16Array[i], true);
|
|
475
|
+
}
|
|
476
|
+
return uint8Array;
|
|
477
|
+
}
|
|
478
|
+
function uint8ToInt16(uint8Array) {
|
|
479
|
+
const int16Array = new Int16Array(uint8Array.length / 2);
|
|
480
|
+
const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
|
|
481
|
+
for (let i = 0; i < int16Array.length; i++) {
|
|
482
|
+
int16Array[i] = view.getInt16(i * 2, true);
|
|
483
|
+
}
|
|
484
|
+
return int16Array;
|
|
485
|
+
}
|
|
486
|
+
function createWavHeader(dataLength, sampleRate, channels, bitDepth) {
|
|
487
|
+
const header = new ArrayBuffer(44);
|
|
488
|
+
const view = new DataView(header);
|
|
489
|
+
const byteRate = sampleRate * channels * bitDepth / 8;
|
|
490
|
+
const blockAlign = channels * bitDepth / 8;
|
|
491
|
+
writeString(view, 0, "RIFF");
|
|
492
|
+
view.setUint32(4, 36 + dataLength, true);
|
|
493
|
+
writeString(view, 8, "WAVE");
|
|
494
|
+
writeString(view, 12, "fmt ");
|
|
495
|
+
view.setUint32(16, 16, true);
|
|
496
|
+
view.setUint16(20, 1, true);
|
|
497
|
+
view.setUint16(22, channels, true);
|
|
498
|
+
view.setUint32(24, sampleRate, true);
|
|
499
|
+
view.setUint32(28, byteRate, true);
|
|
500
|
+
view.setUint16(32, blockAlign, true);
|
|
501
|
+
view.setUint16(34, bitDepth, true);
|
|
502
|
+
writeString(view, 36, "data");
|
|
503
|
+
view.setUint32(40, dataLength, true);
|
|
504
|
+
return new Uint8Array(header);
|
|
505
|
+
}
|
|
506
|
+
function writeString(view, offset, str) {
|
|
507
|
+
for (let i = 0; i < str.length; i++) {
|
|
508
|
+
view.setUint8(offset + i, str.charCodeAt(i));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function wrapPcmInWav(pcmData, options = {}) {
|
|
512
|
+
const opts = { ...DEFAULT_OPTIONS2, ...options };
|
|
513
|
+
const header = createWavHeader(pcmData.length, opts.sampleRate, opts.channels, opts.bitDepth);
|
|
514
|
+
const wav = new Uint8Array(header.length + pcmData.length);
|
|
515
|
+
wav.set(header);
|
|
516
|
+
wav.set(pcmData, header.length);
|
|
517
|
+
return wav;
|
|
518
|
+
}
|
|
519
|
+
function extractPcmFromWav(wavData) {
|
|
520
|
+
const view = new DataView(wavData.buffer, wavData.byteOffset, wavData.byteLength);
|
|
521
|
+
const riff = String.fromCharCode(view.getUint8(0), view.getUint8(1), view.getUint8(2), view.getUint8(3));
|
|
522
|
+
if (riff !== "RIFF") {
|
|
523
|
+
throw new Error("Invalid WAV file: Missing RIFF header");
|
|
524
|
+
}
|
|
525
|
+
const wave = String.fromCharCode(view.getUint8(8), view.getUint8(9), view.getUint8(10), view.getUint8(11));
|
|
526
|
+
if (wave !== "WAVE") {
|
|
527
|
+
throw new Error("Invalid WAV file: Missing WAVE format");
|
|
528
|
+
}
|
|
529
|
+
const channels = view.getUint16(22, true);
|
|
530
|
+
const sampleRate = view.getUint32(24, true);
|
|
531
|
+
const bitDepth = view.getUint16(34, true);
|
|
532
|
+
let dataOffset = 44;
|
|
533
|
+
let dataLength = view.getUint32(40, true);
|
|
534
|
+
if (dataLength === 0 || dataOffset + dataLength > wavData.length) {
|
|
535
|
+
for (let i = 36; i < wavData.length - 8; i++) {
|
|
536
|
+
const chunk = String.fromCharCode(
|
|
537
|
+
view.getUint8(i),
|
|
538
|
+
view.getUint8(i + 1),
|
|
539
|
+
view.getUint8(i + 2),
|
|
540
|
+
view.getUint8(i + 3)
|
|
541
|
+
);
|
|
542
|
+
if (chunk === "data") {
|
|
543
|
+
dataLength = view.getUint32(i + 4, true);
|
|
544
|
+
dataOffset = i + 8;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const pcmData = wavData.slice(dataOffset, dataOffset + dataLength);
|
|
550
|
+
return {
|
|
551
|
+
pcmData,
|
|
552
|
+
sampleRate,
|
|
553
|
+
channels,
|
|
554
|
+
bitDepth
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
var AudioEncoder = class {
|
|
558
|
+
options;
|
|
559
|
+
constructor(options = {}) {
|
|
560
|
+
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Get current options
|
|
564
|
+
*/
|
|
565
|
+
get format() {
|
|
566
|
+
return this.options.format;
|
|
567
|
+
}
|
|
568
|
+
get sampleRate() {
|
|
569
|
+
return this.options.sampleRate;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Encode audio data for transmission
|
|
573
|
+
*/
|
|
574
|
+
encode(data) {
|
|
575
|
+
return encodeAudioToBase64(data);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Decode received audio data
|
|
579
|
+
*/
|
|
580
|
+
decode(base64) {
|
|
581
|
+
return decodeBase64ToAudio(base64);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Convert Float32 samples to transmission-ready format
|
|
585
|
+
*/
|
|
586
|
+
fromFloat32(samples) {
|
|
587
|
+
const int16 = float32ToInt16(samples);
|
|
588
|
+
return int16ToUint8(int16);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Convert received data to Float32 samples
|
|
592
|
+
*/
|
|
593
|
+
toFloat32(data) {
|
|
594
|
+
const int16 = uint8ToInt16(data);
|
|
595
|
+
return int16ToFloat32(int16);
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Wrap data in WAV format if needed
|
|
599
|
+
*/
|
|
600
|
+
wrapWav(data) {
|
|
601
|
+
if (this.options.format === "wav") {
|
|
602
|
+
return wrapPcmInWav(data, this.options);
|
|
603
|
+
}
|
|
604
|
+
return data;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// src/client.ts
|
|
609
|
+
var CONFIG_DEFAULTS = {
|
|
610
|
+
connectionTimeout: 3e4,
|
|
611
|
+
autoReconnect: true,
|
|
612
|
+
maxReconnectAttempts: 5,
|
|
613
|
+
reconnectDelay: 1e3,
|
|
614
|
+
debug: false
|
|
615
|
+
};
|
|
616
|
+
var SESSION_DEFAULTS = {
|
|
617
|
+
voiceId: "en-US-Standard-A",
|
|
618
|
+
languageCode: "en-US",
|
|
619
|
+
inputFormat: "pcm16",
|
|
620
|
+
outputFormat: "pcm16",
|
|
621
|
+
sampleRate: 16e3
|
|
622
|
+
};
|
|
623
|
+
var LiveSpeechClient = class {
|
|
624
|
+
config;
|
|
625
|
+
connection;
|
|
626
|
+
audioEncoder;
|
|
627
|
+
logger;
|
|
628
|
+
sessionId = null;
|
|
629
|
+
sessionConfig = null;
|
|
630
|
+
// Event listeners using a simple map
|
|
631
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
632
|
+
// Simplified handlers
|
|
633
|
+
transcriptHandler = null;
|
|
634
|
+
responseHandler = null;
|
|
635
|
+
audioHandler = null;
|
|
636
|
+
errorHandler = null;
|
|
637
|
+
constructor(config) {
|
|
638
|
+
if (!config.region) {
|
|
639
|
+
throw new Error("region is required");
|
|
640
|
+
}
|
|
641
|
+
if (!config.apiKey) {
|
|
642
|
+
throw new Error("apiKey is required");
|
|
643
|
+
}
|
|
644
|
+
const endpoint = getEndpointForRegion(config.region);
|
|
645
|
+
this.config = {
|
|
646
|
+
endpoint,
|
|
647
|
+
apiKey: config.apiKey,
|
|
648
|
+
connectionTimeout: config.connectionTimeout ?? CONFIG_DEFAULTS.connectionTimeout,
|
|
649
|
+
autoReconnect: config.autoReconnect ?? CONFIG_DEFAULTS.autoReconnect,
|
|
650
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? CONFIG_DEFAULTS.maxReconnectAttempts,
|
|
651
|
+
reconnectDelay: config.reconnectDelay ?? CONFIG_DEFAULTS.reconnectDelay,
|
|
652
|
+
debug: config.debug ?? CONFIG_DEFAULTS.debug
|
|
653
|
+
};
|
|
654
|
+
this.logger = createLogger("LiveSpeech", this.config.debug);
|
|
655
|
+
this.audioEncoder = new AudioEncoder();
|
|
656
|
+
this.connection = new WebSocketConnection(this.config, {
|
|
657
|
+
onOpen: (connectionId) => this.handleConnected(connectionId),
|
|
658
|
+
onClose: (code, reason) => this.handleDisconnected(code, reason),
|
|
659
|
+
onError: (error) => this.handleError("connection_failed", error.message),
|
|
660
|
+
onMessage: (message) => this.handleMessage(message),
|
|
661
|
+
onReconnecting: (attempt, maxAttempts, delay) => this.handleReconnecting(attempt, maxAttempts, delay)
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
// ==================== Public API ====================
|
|
665
|
+
/**
|
|
666
|
+
* Get current connection state
|
|
667
|
+
*/
|
|
668
|
+
get connectionState() {
|
|
669
|
+
return this.connection.currentState;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get connection ID
|
|
673
|
+
*/
|
|
674
|
+
get connectionId() {
|
|
675
|
+
return this.connection.id;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Get current session ID
|
|
679
|
+
*/
|
|
680
|
+
get currentSessionId() {
|
|
681
|
+
return this.sessionId;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Check if connected
|
|
685
|
+
*/
|
|
686
|
+
get isConnected() {
|
|
687
|
+
return this.connection.isConnected;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Check if session is active
|
|
691
|
+
*/
|
|
692
|
+
get hasActiveSession() {
|
|
693
|
+
return this.sessionId !== null;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Connect to the server
|
|
697
|
+
*/
|
|
698
|
+
async connect() {
|
|
699
|
+
this.logger.info("Connecting...");
|
|
700
|
+
await this.connection.connect();
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Disconnect from the server
|
|
704
|
+
*/
|
|
705
|
+
disconnect() {
|
|
706
|
+
this.logger.info("Disconnecting...");
|
|
707
|
+
this.sessionId = null;
|
|
708
|
+
this.sessionConfig = null;
|
|
709
|
+
this.connection.disconnect();
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Start a new session
|
|
713
|
+
*/
|
|
714
|
+
async startSession(config) {
|
|
715
|
+
if (!this.isConnected) {
|
|
716
|
+
throw new Error("Not connected. Call connect() first.");
|
|
717
|
+
}
|
|
718
|
+
if (this.sessionId) {
|
|
719
|
+
throw new Error("Session already active. Call endSession() first.");
|
|
720
|
+
}
|
|
721
|
+
const resolvedConfig = {
|
|
722
|
+
prePrompt: config.prePrompt,
|
|
723
|
+
voiceId: config.voiceId ?? SESSION_DEFAULTS.voiceId,
|
|
724
|
+
languageCode: config.languageCode ?? SESSION_DEFAULTS.languageCode,
|
|
725
|
+
inputFormat: config.inputFormat ?? SESSION_DEFAULTS.inputFormat,
|
|
726
|
+
outputFormat: config.outputFormat ?? SESSION_DEFAULTS.outputFormat,
|
|
727
|
+
sampleRate: config.sampleRate ?? SESSION_DEFAULTS.sampleRate,
|
|
728
|
+
metadata: config.metadata ?? {}
|
|
729
|
+
};
|
|
730
|
+
this.sessionConfig = resolvedConfig;
|
|
731
|
+
this.logger.info("Starting session...");
|
|
732
|
+
return new Promise((resolve, reject) => {
|
|
733
|
+
const onSessionStarted = (event) => {
|
|
734
|
+
this.off("sessionStarted", onSessionStarted);
|
|
735
|
+
this.off("error", onError);
|
|
736
|
+
resolve(event.sessionId);
|
|
737
|
+
};
|
|
738
|
+
const onError = (event) => {
|
|
739
|
+
if (event.code === "session_error") {
|
|
740
|
+
this.off("sessionStarted", onSessionStarted);
|
|
741
|
+
this.off("error", onError);
|
|
742
|
+
reject(new Error(event.message));
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
this.on("sessionStarted", onSessionStarted);
|
|
746
|
+
this.on("error", onError);
|
|
747
|
+
this.connection.send({
|
|
748
|
+
action: "startSession",
|
|
749
|
+
prePrompt: resolvedConfig.prePrompt,
|
|
750
|
+
voiceId: resolvedConfig.voiceId,
|
|
751
|
+
languageCode: resolvedConfig.languageCode,
|
|
752
|
+
inputFormat: resolvedConfig.inputFormat,
|
|
753
|
+
outputFormat: resolvedConfig.outputFormat,
|
|
754
|
+
sampleRate: resolvedConfig.sampleRate,
|
|
755
|
+
metadata: resolvedConfig.metadata
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* End the current session
|
|
761
|
+
*/
|
|
762
|
+
async endSession() {
|
|
763
|
+
if (!this.sessionId) {
|
|
764
|
+
this.logger.warn("No active session to end");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
this.logger.info("Ending session...");
|
|
768
|
+
return new Promise((resolve) => {
|
|
769
|
+
const onSessionEnded = () => {
|
|
770
|
+
this.off("sessionEnded", onSessionEnded);
|
|
771
|
+
resolve();
|
|
772
|
+
};
|
|
773
|
+
this.on("sessionEnded", onSessionEnded);
|
|
774
|
+
this.connection.send({ action: "endSession" });
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Send audio data
|
|
779
|
+
*/
|
|
780
|
+
sendAudio(data, options) {
|
|
781
|
+
if (!this.isConnected) {
|
|
782
|
+
throw new Error("Not connected");
|
|
783
|
+
}
|
|
784
|
+
if (!this.sessionId) {
|
|
785
|
+
throw new Error("No active session. Call startSession() first.");
|
|
786
|
+
}
|
|
787
|
+
const base64Data = this.audioEncoder.encode(data);
|
|
788
|
+
const format = options?.format ?? this.sessionConfig?.inputFormat ?? SESSION_DEFAULTS.inputFormat;
|
|
789
|
+
const sampleRate = this.sessionConfig?.sampleRate ?? SESSION_DEFAULTS.sampleRate;
|
|
790
|
+
const audioMessage = {
|
|
791
|
+
action: "audio",
|
|
792
|
+
data: base64Data,
|
|
793
|
+
format,
|
|
794
|
+
sampleRate
|
|
795
|
+
};
|
|
796
|
+
if (options?.isFinal !== void 0) {
|
|
797
|
+
audioMessage.isFinal = options.isFinal;
|
|
798
|
+
}
|
|
799
|
+
this.connection.send(audioMessage);
|
|
800
|
+
}
|
|
801
|
+
// ==================== Event System ====================
|
|
802
|
+
/**
|
|
803
|
+
* Add event listener
|
|
804
|
+
*/
|
|
805
|
+
on(event, listener) {
|
|
806
|
+
if (!this.eventListeners.has(event)) {
|
|
807
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
808
|
+
}
|
|
809
|
+
this.eventListeners.get(event).add(listener);
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Remove event listener
|
|
813
|
+
*/
|
|
814
|
+
off(event, listener) {
|
|
815
|
+
const listeners = this.eventListeners.get(event);
|
|
816
|
+
if (listeners) {
|
|
817
|
+
listeners.delete(listener);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Set transcript handler (simplified)
|
|
822
|
+
*/
|
|
823
|
+
setTranscriptHandler(handler) {
|
|
824
|
+
this.transcriptHandler = handler;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Set response handler (simplified)
|
|
828
|
+
*/
|
|
829
|
+
setResponseHandler(handler) {
|
|
830
|
+
this.responseHandler = handler;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Set audio handler (simplified)
|
|
834
|
+
*/
|
|
835
|
+
setAudioHandler(handler) {
|
|
836
|
+
this.audioHandler = handler;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Set error handler (simplified)
|
|
840
|
+
*/
|
|
841
|
+
setErrorHandler(handler) {
|
|
842
|
+
this.errorHandler = handler;
|
|
843
|
+
}
|
|
844
|
+
// ==================== Private Methods ====================
|
|
845
|
+
emit(event, data) {
|
|
846
|
+
const listeners = this.eventListeners.get(event);
|
|
847
|
+
if (listeners) {
|
|
848
|
+
listeners.forEach((listener) => {
|
|
849
|
+
try {
|
|
850
|
+
listener(data);
|
|
851
|
+
} catch (error) {
|
|
852
|
+
this.logger.error(`Error in ${event} listener:`, error);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
handleConnected(connectionId) {
|
|
858
|
+
const event = {
|
|
859
|
+
type: "connected",
|
|
860
|
+
connectionId,
|
|
861
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
862
|
+
};
|
|
863
|
+
this.emit("connected", event);
|
|
864
|
+
}
|
|
865
|
+
handleDisconnected(code, _reason) {
|
|
866
|
+
this.sessionId = null;
|
|
867
|
+
this.sessionConfig = null;
|
|
868
|
+
const event = {
|
|
869
|
+
type: "disconnected",
|
|
870
|
+
reason: code === 1e3 ? "normal" : "error",
|
|
871
|
+
code,
|
|
872
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
873
|
+
};
|
|
874
|
+
this.emit("disconnected", event);
|
|
875
|
+
}
|
|
876
|
+
handleReconnecting(attempt, maxAttempts, delay) {
|
|
877
|
+
const event = {
|
|
878
|
+
type: "reconnecting",
|
|
879
|
+
attempt,
|
|
880
|
+
maxAttempts,
|
|
881
|
+
delay,
|
|
882
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
883
|
+
};
|
|
884
|
+
this.emit("reconnecting", event);
|
|
885
|
+
}
|
|
886
|
+
handleError(code, message, details) {
|
|
887
|
+
const event = {
|
|
888
|
+
type: "error",
|
|
889
|
+
code,
|
|
890
|
+
message,
|
|
891
|
+
details,
|
|
892
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
893
|
+
};
|
|
894
|
+
this.emit("error", event);
|
|
895
|
+
this.errorHandler?.(event);
|
|
896
|
+
}
|
|
897
|
+
handleMessage(message) {
|
|
898
|
+
switch (message.type) {
|
|
899
|
+
case "sessionStarted":
|
|
900
|
+
this.sessionId = message.sessionId;
|
|
901
|
+
this.emit("sessionStarted", {
|
|
902
|
+
type: "sessionStarted",
|
|
903
|
+
sessionId: message.sessionId,
|
|
904
|
+
timestamp: message.timestamp
|
|
905
|
+
});
|
|
906
|
+
break;
|
|
907
|
+
case "sessionEnded":
|
|
908
|
+
this.sessionId = null;
|
|
909
|
+
this.sessionConfig = null;
|
|
910
|
+
this.emit("sessionEnded", {
|
|
911
|
+
type: "sessionEnded",
|
|
912
|
+
sessionId: message.sessionId,
|
|
913
|
+
timestamp: message.timestamp
|
|
914
|
+
});
|
|
915
|
+
break;
|
|
916
|
+
case "transcript": {
|
|
917
|
+
const transcriptEvent = {
|
|
918
|
+
type: "transcript",
|
|
919
|
+
text: message.text,
|
|
920
|
+
isFinal: message.isFinal,
|
|
921
|
+
timestamp: message.timestamp
|
|
922
|
+
};
|
|
923
|
+
if (message.confidence !== void 0) {
|
|
924
|
+
transcriptEvent.confidence = message.confidence;
|
|
925
|
+
}
|
|
926
|
+
this.emit("transcript", transcriptEvent);
|
|
927
|
+
this.transcriptHandler?.(message.text, message.isFinal);
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case "response": {
|
|
931
|
+
const responseEvent = {
|
|
932
|
+
type: "response",
|
|
933
|
+
text: message.text,
|
|
934
|
+
isFinal: message.isFinal,
|
|
935
|
+
timestamp: message.timestamp
|
|
936
|
+
};
|
|
937
|
+
this.emit("response", responseEvent);
|
|
938
|
+
this.responseHandler?.(message.text, message.isFinal);
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
case "audio": {
|
|
942
|
+
const audioData = this.audioEncoder.decode(message.data);
|
|
943
|
+
const audioEvent = {
|
|
944
|
+
type: "audio",
|
|
945
|
+
data: audioData,
|
|
946
|
+
format: message.format,
|
|
947
|
+
sampleRate: message.sampleRate,
|
|
948
|
+
timestamp: message.timestamp
|
|
949
|
+
};
|
|
950
|
+
this.emit("audio", audioEvent);
|
|
951
|
+
this.audioHandler?.(audioData);
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
case "error":
|
|
955
|
+
this.handleError(message.code, message.message, message.details);
|
|
956
|
+
break;
|
|
957
|
+
default:
|
|
958
|
+
this.logger.warn("Unknown message type:", message.type);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
963
|
+
0 && (module.exports = {
|
|
964
|
+
AudioEncoder,
|
|
965
|
+
LiveSpeechClient,
|
|
966
|
+
Region,
|
|
967
|
+
createWavHeader,
|
|
968
|
+
decodeBase64ToAudio,
|
|
969
|
+
encodeAudioToBase64,
|
|
970
|
+
extractPcmFromWav,
|
|
971
|
+
float32ToInt16,
|
|
972
|
+
getEndpointForRegion,
|
|
973
|
+
int16ToFloat32,
|
|
974
|
+
int16ToUint8,
|
|
975
|
+
isValidRegion,
|
|
976
|
+
uint8ToInt16,
|
|
977
|
+
wrapPcmInWav
|
|
978
|
+
});
|