@cartesia/cartesia-js 1.0.0-alpha.1 → 1.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +26 -26
  2. package/CHANGELOG.md +12 -0
  3. package/dist/chunk-2BFEKY3F.js +16 -0
  4. package/dist/{chunk-WE63M7PJ.js → chunk-36JBKJUN.js} +2 -2
  5. package/dist/{chunk-NDNN326Q.js → chunk-IQAXBRHU.js} +9 -5
  6. package/dist/{chunk-X7SJMF2R.js → chunk-ISRU7PLL.js} +3 -3
  7. package/dist/{chunk-BCQ63627.js → chunk-PQ5EVEEH.js} +4 -2
  8. package/dist/{chunk-JOHSCOLW.js → chunk-PQ6CIPFW.js} +20 -6
  9. package/dist/{chunk-LYPTISWL.js → chunk-RO7TY474.js} +13 -7
  10. package/dist/{chunk-WBK6LLXX.js → chunk-SGXUEFII.js} +1 -1
  11. package/dist/{chunk-4RMSIQLG.js → chunk-VK7LBMVI.js} +2 -2
  12. package/dist/index.cjs +47 -25
  13. package/dist/index.js +9 -9
  14. package/dist/lib/client.cjs +9 -10
  15. package/dist/lib/client.js +2 -2
  16. package/dist/lib/constants.cjs +9 -9
  17. package/dist/lib/constants.d.cts +15 -3
  18. package/dist/lib/constants.d.ts +15 -3
  19. package/dist/lib/constants.js +3 -1
  20. package/dist/lib/index.cjs +46 -24
  21. package/dist/lib/index.js +8 -8
  22. package/dist/react/index.cjs +63 -33
  23. package/dist/react/index.js +25 -17
  24. package/dist/tts/index.cjs +46 -24
  25. package/dist/tts/index.js +6 -6
  26. package/dist/tts/player.cjs +1 -1
  27. package/dist/tts/player.js +2 -2
  28. package/dist/tts/source.cjs +20 -6
  29. package/dist/tts/source.js +1 -1
  30. package/dist/tts/utils.cjs +13 -7
  31. package/dist/tts/utils.js +1 -1
  32. package/dist/tts/websocket.cjs +46 -24
  33. package/dist/tts/websocket.js +5 -5
  34. package/dist/voices/index.cjs +9 -10
  35. package/dist/voices/index.js +3 -3
  36. package/package.json +1 -1
  37. package/src/lib/client.ts +3 -2
  38. package/src/lib/constants.ts +18 -9
  39. package/src/react/index.ts +20 -8
  40. package/src/tts/player.ts +2 -1
  41. package/src/tts/source.ts +22 -5
  42. package/src/tts/utils.ts +15 -7
  43. package/src/tts/websocket.ts +5 -2
  44. package/dist/chunk-3GBZUGUD.js +0 -17
@@ -72,7 +72,7 @@ __export(source_exports, {
72
72
  });
73
73
  module.exports = __toCommonJS(source_exports);
74
74
  var import_emittery = __toESM(require("emittery"), 1);
75
- var _emitter, _buffer, _readIndex, _closed, _sampleRate;
75
+ var _emitter, _buffer, _readIndex, _writeIndex, _closed, _sampleRate;
76
76
  var Source = class {
77
77
  /**
78
78
  * Create a new Source.
@@ -82,8 +82,9 @@ var Source = class {
82
82
  */
83
83
  constructor({ sampleRate }) {
84
84
  __privateAdd(this, _emitter, new import_emittery.default());
85
- __privateAdd(this, _buffer, new Float32Array());
85
+ __privateAdd(this, _buffer, void 0);
86
86
  __privateAdd(this, _readIndex, 0);
87
+ __privateAdd(this, _writeIndex, 0);
87
88
  __privateAdd(this, _closed, false);
88
89
  __privateAdd(this, _sampleRate, void 0);
89
90
  this.on = __privateGet(this, _emitter).on.bind(__privateGet(this, _emitter));
@@ -91,6 +92,7 @@ var Source = class {
91
92
  this.events = __privateGet(this, _emitter).events.bind(__privateGet(this, _emitter));
92
93
  this.off = __privateGet(this, _emitter).off.bind(__privateGet(this, _emitter));
93
94
  __privateSet(this, _sampleRate, sampleRate);
95
+ __privateSet(this, _buffer, new Float32Array(1024));
94
96
  }
95
97
  get sampleRate() {
96
98
  return __privateGet(this, _sampleRate);
@@ -102,7 +104,18 @@ var Source = class {
102
104
  */
103
105
  enqueue(src) {
104
106
  return __async(this, null, function* () {
105
- __privateSet(this, _buffer, new Float32Array([...__privateGet(this, _buffer), ...src]));
107
+ const requiredCapacity = __privateGet(this, _writeIndex) + src.length;
108
+ if (requiredCapacity > __privateGet(this, _buffer).length) {
109
+ let newCapacity = __privateGet(this, _buffer).length;
110
+ while (newCapacity < requiredCapacity) {
111
+ newCapacity *= 2;
112
+ }
113
+ const newBuffer = new Float32Array(newCapacity);
114
+ newBuffer.set(__privateGet(this, _buffer));
115
+ __privateSet(this, _buffer, newBuffer);
116
+ }
117
+ __privateGet(this, _buffer).set(src, __privateGet(this, _writeIndex));
118
+ __privateSet(this, _writeIndex, __privateGet(this, _writeIndex) + src.length);
106
119
  yield __privateGet(this, _emitter).emit("enqueue");
107
120
  });
108
121
  }
@@ -116,7 +129,7 @@ var Source = class {
116
129
  read(dst) {
117
130
  return __async(this, null, function* () {
118
131
  const targetReadIndex = __privateGet(this, _readIndex) + dst.length;
119
- while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _buffer).length) {
132
+ while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _writeIndex)) {
120
133
  yield __privateGet(this, _emitter).emit("wait");
121
134
  yield Promise.race([
122
135
  __privateGet(this, _emitter).once("enqueue"),
@@ -124,8 +137,8 @@ var Source = class {
124
137
  ]);
125
138
  yield __privateGet(this, _emitter).emit("read");
126
139
  }
127
- const read = Math.min(dst.length, __privateGet(this, _buffer).length - __privateGet(this, _readIndex));
128
- dst.set(__privateGet(this, _buffer).slice(__privateGet(this, _readIndex), __privateGet(this, _readIndex) + read));
140
+ const read = Math.min(dst.length, __privateGet(this, _writeIndex) - __privateGet(this, _readIndex));
141
+ dst.set(__privateGet(this, _buffer).subarray(__privateGet(this, _readIndex), __privateGet(this, _readIndex) + read));
129
142
  __privateSet(this, _readIndex, __privateGet(this, _readIndex) + read);
130
143
  return read;
131
144
  });
@@ -163,5 +176,6 @@ var Source = class {
163
176
  _emitter = new WeakMap();
164
177
  _buffer = new WeakMap();
165
178
  _readIndex = new WeakMap();
179
+ _writeIndex = new WeakMap();
166
180
  _closed = new WeakMap();
167
181
  _sampleRate = new WeakMap();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Source
3
- } from "../chunk-JOHSCOLW.js";
3
+ } from "../chunk-PQ6CIPFW.js";
4
4
  import "../chunk-WIFMLPT5.js";
5
5
  export {
6
6
  Source as default
@@ -42,13 +42,19 @@ __export(utils_exports, {
42
42
  module.exports = __toCommonJS(utils_exports);
43
43
  var import_base64_js = __toESM(require("base64-js"), 1);
44
44
  function base64ToArray(b64) {
45
- return filterSentinel(b64).reduce((acc, b) => {
46
- const floats = new Float32Array(import_base64_js.default.toByteArray(b).buffer);
47
- const newAcc = new Float32Array(acc.length + floats.length);
48
- newAcc.set(acc, 0);
49
- newAcc.set(floats, acc.length);
50
- return newAcc;
51
- }, new Float32Array(0));
45
+ const byteArrays = filterSentinel(b64).map((b) => import_base64_js.default.toByteArray(b));
46
+ const totalLength = byteArrays.reduce(
47
+ (acc, arr) => acc + arr.length / Float32Array.BYTES_PER_ELEMENT,
48
+ 0
49
+ );
50
+ const result = new Float32Array(totalLength);
51
+ let offset = 0;
52
+ for (const arr of byteArrays) {
53
+ const floats = new Float32Array(arr.buffer);
54
+ result.set(floats, offset);
55
+ offset += floats.length;
56
+ }
57
+ return result;
52
58
  }
53
59
  function playAudioBuffer(floats, context, startAt, sampleRate) {
54
60
  const source = context.createBufferSource();
package/dist/tts/utils.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  isComplete,
8
8
  isSentinel,
9
9
  playAudioBuffer
10
- } from "../chunk-LYPTISWL.js";
10
+ } from "../chunk-RO7TY474.js";
11
11
  import "../chunk-WIFMLPT5.js";
12
12
  export {
13
13
  base64ToArray,
@@ -100,16 +100,14 @@ var import_partysocket = require("partysocket");
100
100
  var import_cross_fetch = __toESM(require("cross-fetch"), 1);
101
101
 
102
102
  // src/lib/constants.ts
103
- var BASE_URL = "https://api.cartesia.ai/v0";
104
- var constructApiUrl = (baseUrl, path, protocol) => {
105
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
106
- if (!protocol) {
107
- return new URL(`${baseUrl}${normalizedPath}`);
103
+ var BASE_URL = "https://api.cartesia.ai";
104
+ var CARTESIA_VERSION = "2024-06-10";
105
+ var constructApiUrl = (baseUrl, path, { websocket = false } = {}) => {
106
+ const url = new URL(path, baseUrl);
107
+ if (websocket) {
108
+ url.protocol = baseUrl.replace(/^http/, "ws");
108
109
  }
109
- if (!["http", "ws"].includes(protocol)) {
110
- throw new Error(`Invalid protocol: ${protocol}`);
111
- }
112
- return new URL(`${baseUrl.replace(/^http/, protocol)}${normalizedPath}`);
110
+ return url;
113
111
  };
114
112
 
115
113
  // src/lib/client.ts
@@ -125,7 +123,8 @@ var Client = class {
125
123
  const url = constructApiUrl(this.baseUrl, path);
126
124
  return (0, import_cross_fetch.default)(url.toString(), __spreadProps(__spreadValues({}, options), {
127
125
  headers: __spreadValues({
128
- "X-API-KEY": this.apiKey
126
+ "X-API-Key": this.apiKey,
127
+ "Cartesia-Version": CARTESIA_VERSION
129
128
  }, options.headers)
130
129
  }));
131
130
  }
@@ -133,7 +132,7 @@ var Client = class {
133
132
 
134
133
  // src/tts/source.ts
135
134
  var import_emittery = __toESM(require("emittery"), 1);
136
- var _emitter, _buffer, _readIndex, _closed, _sampleRate;
135
+ var _emitter, _buffer, _readIndex, _writeIndex, _closed, _sampleRate;
137
136
  var Source = class {
138
137
  /**
139
138
  * Create a new Source.
@@ -143,8 +142,9 @@ var Source = class {
143
142
  */
144
143
  constructor({ sampleRate }) {
145
144
  __privateAdd(this, _emitter, new import_emittery.default());
146
- __privateAdd(this, _buffer, new Float32Array());
145
+ __privateAdd(this, _buffer, void 0);
147
146
  __privateAdd(this, _readIndex, 0);
147
+ __privateAdd(this, _writeIndex, 0);
148
148
  __privateAdd(this, _closed, false);
149
149
  __privateAdd(this, _sampleRate, void 0);
150
150
  this.on = __privateGet(this, _emitter).on.bind(__privateGet(this, _emitter));
@@ -152,6 +152,7 @@ var Source = class {
152
152
  this.events = __privateGet(this, _emitter).events.bind(__privateGet(this, _emitter));
153
153
  this.off = __privateGet(this, _emitter).off.bind(__privateGet(this, _emitter));
154
154
  __privateSet(this, _sampleRate, sampleRate);
155
+ __privateSet(this, _buffer, new Float32Array(1024));
155
156
  }
156
157
  get sampleRate() {
157
158
  return __privateGet(this, _sampleRate);
@@ -163,7 +164,18 @@ var Source = class {
163
164
  */
164
165
  enqueue(src) {
165
166
  return __async(this, null, function* () {
166
- __privateSet(this, _buffer, new Float32Array([...__privateGet(this, _buffer), ...src]));
167
+ const requiredCapacity = __privateGet(this, _writeIndex) + src.length;
168
+ if (requiredCapacity > __privateGet(this, _buffer).length) {
169
+ let newCapacity = __privateGet(this, _buffer).length;
170
+ while (newCapacity < requiredCapacity) {
171
+ newCapacity *= 2;
172
+ }
173
+ const newBuffer = new Float32Array(newCapacity);
174
+ newBuffer.set(__privateGet(this, _buffer));
175
+ __privateSet(this, _buffer, newBuffer);
176
+ }
177
+ __privateGet(this, _buffer).set(src, __privateGet(this, _writeIndex));
178
+ __privateSet(this, _writeIndex, __privateGet(this, _writeIndex) + src.length);
167
179
  yield __privateGet(this, _emitter).emit("enqueue");
168
180
  });
169
181
  }
@@ -177,7 +189,7 @@ var Source = class {
177
189
  read(dst) {
178
190
  return __async(this, null, function* () {
179
191
  const targetReadIndex = __privateGet(this, _readIndex) + dst.length;
180
- while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _buffer).length) {
192
+ while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _writeIndex)) {
181
193
  yield __privateGet(this, _emitter).emit("wait");
182
194
  yield Promise.race([
183
195
  __privateGet(this, _emitter).once("enqueue"),
@@ -185,8 +197,8 @@ var Source = class {
185
197
  ]);
186
198
  yield __privateGet(this, _emitter).emit("read");
187
199
  }
188
- const read = Math.min(dst.length, __privateGet(this, _buffer).length - __privateGet(this, _readIndex));
189
- dst.set(__privateGet(this, _buffer).slice(__privateGet(this, _readIndex), __privateGet(this, _readIndex) + read));
200
+ const read = Math.min(dst.length, __privateGet(this, _writeIndex) - __privateGet(this, _readIndex));
201
+ dst.set(__privateGet(this, _buffer).subarray(__privateGet(this, _readIndex), __privateGet(this, _readIndex) + read));
190
202
  __privateSet(this, _readIndex, __privateGet(this, _readIndex) + read);
191
203
  return read;
192
204
  });
@@ -224,19 +236,26 @@ var Source = class {
224
236
  _emitter = new WeakMap();
225
237
  _buffer = new WeakMap();
226
238
  _readIndex = new WeakMap();
239
+ _writeIndex = new WeakMap();
227
240
  _closed = new WeakMap();
228
241
  _sampleRate = new WeakMap();
229
242
 
230
243
  // src/tts/utils.ts
231
244
  var import_base64_js = __toESM(require("base64-js"), 1);
232
245
  function base64ToArray(b64) {
233
- return filterSentinel(b64).reduce((acc, b) => {
234
- const floats = new Float32Array(import_base64_js.default.toByteArray(b).buffer);
235
- const newAcc = new Float32Array(acc.length + floats.length);
236
- newAcc.set(acc, 0);
237
- newAcc.set(floats, acc.length);
238
- return newAcc;
239
- }, new Float32Array(0));
246
+ const byteArrays = filterSentinel(b64).map((b) => import_base64_js.default.toByteArray(b));
247
+ const totalLength = byteArrays.reduce(
248
+ (acc, arr) => acc + arr.length / Float32Array.BYTES_PER_ELEMENT,
249
+ 0
250
+ );
251
+ const result = new Float32Array(totalLength);
252
+ let offset = 0;
253
+ for (const arr of byteArrays) {
254
+ const floats = new Float32Array(arr.buffer);
255
+ result.set(floats, offset);
256
+ offset += floats.length;
257
+ }
258
+ return result;
240
259
  }
241
260
  function createMessageHandlerForContextId(contextId, handler) {
242
261
  return (event) => {
@@ -386,8 +405,11 @@ var WebSocket = class extends Client {
386
405
  * @throws {Error} If the WebSocket fails to connect.
387
406
  */
388
407
  connect() {
389
- const url = constructApiUrl(this.baseUrl, "/tts/websocket", "ws");
408
+ const url = constructApiUrl(this.baseUrl, "/tts/websocket", {
409
+ websocket: true
410
+ });
390
411
  url.searchParams.set("api_key", this.apiKey);
412
+ url.searchParams.set("cartesia_version", CARTESIA_VERSION);
391
413
  const emitter = new import_emittery2.default();
392
414
  this.socket = new import_partysocket.WebSocket(url.toString());
393
415
  this.socket.onopen = () => {
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  WebSocket
3
- } from "../chunk-NDNN326Q.js";
4
- import "../chunk-JOHSCOLW.js";
5
- import "../chunk-BCQ63627.js";
6
- import "../chunk-3GBZUGUD.js";
7
- import "../chunk-LYPTISWL.js";
3
+ } from "../chunk-IQAXBRHU.js";
4
+ import "../chunk-PQ6CIPFW.js";
5
+ import "../chunk-PQ5EVEEH.js";
6
+ import "../chunk-2BFEKY3F.js";
7
+ import "../chunk-RO7TY474.js";
8
8
  import "../chunk-WIFMLPT5.js";
9
9
  export {
10
10
  WebSocket as default
@@ -75,16 +75,14 @@ module.exports = __toCommonJS(voices_exports);
75
75
  var import_cross_fetch = __toESM(require("cross-fetch"), 1);
76
76
 
77
77
  // src/lib/constants.ts
78
- var BASE_URL = "https://api.cartesia.ai/v0";
79
- var constructApiUrl = (baseUrl, path, protocol) => {
80
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
81
- if (!protocol) {
82
- return new URL(`${baseUrl}${normalizedPath}`);
78
+ var BASE_URL = "https://api.cartesia.ai";
79
+ var CARTESIA_VERSION = "2024-06-10";
80
+ var constructApiUrl = (baseUrl, path, { websocket = false } = {}) => {
81
+ const url = new URL(path, baseUrl);
82
+ if (websocket) {
83
+ url.protocol = baseUrl.replace(/^http/, "ws");
83
84
  }
84
- if (!["http", "ws"].includes(protocol)) {
85
- throw new Error(`Invalid protocol: ${protocol}`);
86
- }
87
- return new URL(`${baseUrl.replace(/^http/, protocol)}${normalizedPath}`);
85
+ return url;
88
86
  };
89
87
 
90
88
  // src/lib/client.ts
@@ -100,7 +98,8 @@ var Client = class {
100
98
  const url = constructApiUrl(this.baseUrl, path);
101
99
  return (0, import_cross_fetch.default)(url.toString(), __spreadProps(__spreadValues({}, options), {
102
100
  headers: __spreadValues({
103
- "X-API-KEY": this.apiKey
101
+ "X-API-Key": this.apiKey,
102
+ "Cartesia-Version": CARTESIA_VERSION
104
103
  }, options.headers)
105
104
  }));
106
105
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  Voices
3
- } from "../chunk-WBK6LLXX.js";
4
- import "../chunk-BCQ63627.js";
5
- import "../chunk-3GBZUGUD.js";
3
+ } from "../chunk-SGXUEFII.js";
4
+ import "../chunk-PQ5EVEEH.js";
5
+ import "../chunk-2BFEKY3F.js";
6
6
  import "../chunk-WIFMLPT5.js";
7
7
  export {
8
8
  Voices as default
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "Cartesia",
5
5
  "url": "https://cartesia.ai"
6
6
  },
7
- "version": "1.0.0-alpha.1",
7
+ "version": "1.0.0-alpha.3",
8
8
  "description": "Client for the Cartesia API.",
9
9
  "type": "module",
10
10
  "module": "./dist/index.js",
package/src/lib/client.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import fetch from "cross-fetch";
2
2
  import type { ClientOptions } from "../types";
3
- import { BASE_URL, constructApiUrl } from "./constants";
3
+ import { BASE_URL, CARTESIA_VERSION, constructApiUrl } from "./constants";
4
4
 
5
5
  export class Client {
6
6
  apiKey: string;
@@ -22,7 +22,8 @@ export class Client {
22
22
  return fetch(url.toString(), {
23
23
  ...options,
24
24
  headers: {
25
- "X-API-KEY": this.apiKey,
25
+ "X-API-Key": this.apiKey,
26
+ "Cartesia-Version": CARTESIA_VERSION,
26
27
  ...options.headers,
27
28
  },
28
29
  });
@@ -1,16 +1,25 @@
1
- export const BASE_URL = "https://api.cartesia.ai/v0";
1
+ export const BASE_URL = "https://api.cartesia.ai";
2
+ export const CARTESIA_VERSION = "2024-06-10";
2
3
 
4
+ /**
5
+ * Construct a URL for the Cartesia API.
6
+ *
7
+ * @param baseUrl The base URL for the API.
8
+ * @param path The path to append to the base URL.
9
+ * @param options Options for the URL.
10
+ * @param options.websocket Whether to use the WebSocket protocol.
11
+ * @returns A URL object.
12
+ */
3
13
  export const constructApiUrl = (
4
14
  baseUrl: string,
5
15
  path: string,
6
- protocol?: string,
16
+ { websocket = false } = {},
7
17
  ) => {
8
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
9
- if (!protocol) {
10
- return new URL(`${baseUrl}${normalizedPath}`);
18
+ const url = new URL(path, baseUrl);
19
+ if (websocket) {
20
+ // Using find-and-replace ensures that if the base URL uses TLS, the
21
+ // new protocol does too.
22
+ url.protocol = baseUrl.replace(/^http/, "ws");
11
23
  }
12
- if (!["http", "ws"].includes(protocol)) {
13
- throw new Error(`Invalid protocol: ${protocol}`);
14
- }
15
- return new URL(`${baseUrl.replace(/^http/, protocol)}${normalizedPath}`);
24
+ return url;
16
25
  };
@@ -1,3 +1,4 @@
1
+ import type { UnsubscribeFunction } from "emittery";
1
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
3
  import { Cartesia } from "../lib";
3
4
  import Player from "../tts/player";
@@ -91,11 +92,12 @@ export function useTTS({
91
92
  if (!websocketReturn.current) {
92
93
  return;
93
94
  }
94
- websocketReturn.current.on("message", (message) => {
95
+ const unsubscribe = websocketReturn.current.on("message", (message) => {
95
96
  setMessages((messages) => [...messages, JSON.parse(message)]);
96
97
  });
97
98
  await websocketReturn.current.source.once("close");
98
99
  setBufferStatus("buffered");
100
+ unsubscribe();
99
101
  },
100
102
  [websocket],
101
103
  );
@@ -121,13 +123,21 @@ export function useTTS({
121
123
  if (!connection) {
122
124
  return;
123
125
  }
126
+ const unsubscribes = <UnsubscribeFunction[]>[];
127
+ // The await ensures that the connection is open, so we already know that we are connected.
124
128
  setIsConnected(true);
125
- connection.on("open", () => {
126
- setIsConnected(true);
127
- });
128
- const unsubscribe = connection.on("close", () => {
129
- setIsConnected(false);
130
- });
129
+ // If the WebSocket is the kind that automatically reconnects, we need an additional
130
+ // listener for the open event to update the connection status.
131
+ unsubscribes.push(
132
+ connection.on("open", () => {
133
+ setIsConnected(true);
134
+ }),
135
+ );
136
+ unsubscribes.push(
137
+ connection.on("close", () => {
138
+ setIsConnected(false);
139
+ }),
140
+ );
131
141
  const intervalId = setInterval(() => {
132
142
  if (baseUrl) {
133
143
  pingServer(new URL(baseUrl).origin).then((ping) => {
@@ -144,7 +154,9 @@ export function useTTS({
144
154
  }
145
155
  }, PING_INTERVAL);
146
156
  return () => {
147
- unsubscribe();
157
+ for (const unsubscribe of unsubscribes) {
158
+ unsubscribe();
159
+ }
148
160
  clearInterval(intervalId);
149
161
  websocket?.disconnect();
150
162
  };
package/src/tts/player.ts CHANGED
@@ -54,7 +54,8 @@ export default class Player {
54
54
  // If we've reached the end of the source, then read < buffer.length.
55
55
  // In that case, we don't want to play the entire buffer, as that
56
56
  // will cause repeated audio.
57
- const playableAudio = buffer.slice(0, read);
57
+ // So we set the buffer to the correct length.
58
+ const playableAudio = buffer.subarray(0, read);
58
59
  plays.push(this.#playBuffer(playableAudio, source.sampleRate));
59
60
 
60
61
  if (read < buffer.length) {
package/src/tts/source.ts CHANGED
@@ -3,8 +3,9 @@ import type { SourceEventData } from "../types";
3
3
 
4
4
  export default class Source {
5
5
  #emitter = new Emittery<SourceEventData>();
6
- #buffer = new Float32Array();
6
+ #buffer: Float32Array;
7
7
  #readIndex = 0;
8
+ #writeIndex = 0;
8
9
  #closed = false;
9
10
  #sampleRate: number;
10
11
 
@@ -21,6 +22,7 @@ export default class Source {
21
22
  */
22
23
  constructor({ sampleRate }: { sampleRate: number }) {
23
24
  this.#sampleRate = sampleRate;
25
+ this.#buffer = new Float32Array(1024); // Initial size, can be adjusted
24
26
  }
25
27
 
26
28
  get sampleRate() {
@@ -33,8 +35,23 @@ export default class Source {
33
35
  * @param src The audio to append.
34
36
  */
35
37
  async enqueue(src: Float32Array) {
38
+ const requiredCapacity = this.#writeIndex + src.length;
39
+
40
+ // Resize buffer if necessary
41
+ if (requiredCapacity > this.#buffer.length) {
42
+ let newCapacity = this.#buffer.length;
43
+ while (newCapacity < requiredCapacity) {
44
+ newCapacity *= 2; // Double the buffer size
45
+ }
46
+
47
+ const newBuffer = new Float32Array(newCapacity);
48
+ newBuffer.set(this.#buffer);
49
+ this.#buffer = newBuffer;
50
+ }
51
+
36
52
  // Append the audio to the buffer.
37
- this.#buffer = new Float32Array([...this.#buffer, ...src]);
53
+ this.#buffer.set(src, this.#writeIndex);
54
+ this.#writeIndex += src.length;
38
55
  await this.#emitter.emit("enqueue");
39
56
  }
40
57
 
@@ -49,7 +66,7 @@ export default class Source {
49
66
  // Read the buffer into the provided buffer.
50
67
  const targetReadIndex = this.#readIndex + dst.length;
51
68
 
52
- while (!this.#closed && targetReadIndex > this.#buffer.length) {
69
+ while (!this.#closed && targetReadIndex > this.#writeIndex) {
53
70
  // Wait for more audio to be enqueued.
54
71
  await this.#emitter.emit("wait");
55
72
  await Promise.race([
@@ -59,8 +76,8 @@ export default class Source {
59
76
  await this.#emitter.emit("read");
60
77
  }
61
78
 
62
- const read = Math.min(dst.length, this.#buffer.length - this.#readIndex);
63
- dst.set(this.#buffer.slice(this.#readIndex, this.#readIndex + read));
79
+ const read = Math.min(dst.length, this.#writeIndex - this.#readIndex);
80
+ dst.set(this.#buffer.subarray(this.#readIndex, this.#readIndex + read));
64
81
  this.#readIndex += read;
65
82
  return read;
66
83
  }
package/src/tts/utils.ts CHANGED
@@ -10,13 +10,21 @@ import type { Chunk, EmitteryCallbacks, Sentinel } from "../types";
10
10
  * @returns The audio buffer(s) as a Float32Array.
11
11
  */
12
12
  export function base64ToArray(b64: Chunk[]): Float32Array {
13
- return filterSentinel(b64).reduce((acc, b) => {
14
- const floats = new Float32Array(base64.toByteArray(b).buffer);
15
- const newAcc = new Float32Array(acc.length + floats.length);
16
- newAcc.set(acc, 0);
17
- newAcc.set(floats, acc.length);
18
- return newAcc;
19
- }, new Float32Array(0));
13
+ const byteArrays = filterSentinel(b64).map((b) => base64.toByteArray(b));
14
+ const totalLength = byteArrays.reduce(
15
+ (acc, arr) => acc + arr.length / Float32Array.BYTES_PER_ELEMENT,
16
+ 0,
17
+ );
18
+ const result = new Float32Array(totalLength);
19
+
20
+ let offset = 0;
21
+ for (const arr of byteArrays) {
22
+ const floats = new Float32Array(arr.buffer);
23
+ result.set(floats, offset);
24
+ offset += floats.length;
25
+ }
26
+
27
+ return result;
20
28
  }
21
29
 
22
30
  /**
@@ -2,7 +2,7 @@ import Emittery from "emittery";
2
2
  import { humanId } from "human-id";
3
3
  import { WebSocket as PartySocketWebSocket } from "partysocket";
4
4
  import { Client } from "../lib/client";
5
- import { constructApiUrl } from "../lib/constants";
5
+ import { CARTESIA_VERSION, constructApiUrl } from "../lib/constants";
6
6
  import type {
7
7
  ConnectionEventData,
8
8
  EmitteryCallbacks,
@@ -150,8 +150,11 @@ export default class WebSocket extends Client {
150
150
  * @throws {Error} If the WebSocket fails to connect.
151
151
  */
152
152
  connect() {
153
- const url = constructApiUrl(this.baseUrl, "/tts/websocket", "ws");
153
+ const url = constructApiUrl(this.baseUrl, "/tts/websocket", {
154
+ websocket: true,
155
+ });
154
156
  url.searchParams.set("api_key", this.apiKey);
157
+ url.searchParams.set("cartesia_version", CARTESIA_VERSION);
155
158
  const emitter = new Emittery<ConnectionEventData>();
156
159
  this.socket = new PartySocketWebSocket(url.toString());
157
160
  this.socket.onopen = () => {
@@ -1,17 +0,0 @@
1
- // src/lib/constants.ts
2
- var BASE_URL = "https://api.cartesia.ai/v0";
3
- var constructApiUrl = (baseUrl, path, protocol) => {
4
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
5
- if (!protocol) {
6
- return new URL(`${baseUrl}${normalizedPath}`);
7
- }
8
- if (!["http", "ws"].includes(protocol)) {
9
- throw new Error(`Invalid protocol: ${protocol}`);
10
- }
11
- return new URL(`${baseUrl.replace(/^http/, protocol)}${normalizedPath}`);
12
- };
13
-
14
- export {
15
- BASE_URL,
16
- constructApiUrl
17
- };