@cartesia/cartesia-js 1.0.0-alpha.1 → 1.0.0-alpha.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.
Files changed (43) hide show
  1. package/.turbo/turbo-build.log +36 -36
  2. package/CHANGELOG.md +6 -0
  3. package/dist/{chunk-WE63M7PJ.js → chunk-36JBKJUN.js} +2 -2
  4. package/dist/{chunk-NDNN326Q.js → chunk-3F5E46FT.js} +10 -5
  5. package/dist/{chunk-BCQ63627.js → chunk-JGP5BIUV.js} +4 -2
  6. package/dist/{chunk-4RMSIQLG.js → chunk-KWBSQZTY.js} +2 -2
  7. package/dist/{chunk-JOHSCOLW.js → chunk-PQ6CIPFW.js} +20 -6
  8. package/dist/{chunk-LYPTISWL.js → chunk-RO7TY474.js} +13 -7
  9. package/dist/{chunk-X7SJMF2R.js → chunk-T3RG6WV4.js} +3 -3
  10. package/dist/{chunk-WBK6LLXX.js → chunk-WVTITUXX.js} +1 -1
  11. package/dist/{chunk-3GBZUGUD.js → chunk-XHTDPLFR.js} +3 -1
  12. package/dist/index.cjs +43 -17
  13. package/dist/index.js +9 -9
  14. package/dist/lib/client.cjs +4 -2
  15. package/dist/lib/client.js +2 -2
  16. package/dist/lib/constants.cjs +4 -1
  17. package/dist/lib/constants.d.cts +3 -2
  18. package/dist/lib/constants.d.ts +3 -2
  19. package/dist/lib/constants.js +3 -1
  20. package/dist/lib/index.cjs +42 -16
  21. package/dist/lib/index.js +8 -8
  22. package/dist/react/index.cjs +59 -25
  23. package/dist/react/index.js +25 -17
  24. package/dist/tts/index.cjs +42 -16
  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 +42 -16
  33. package/dist/tts/websocket.js +5 -5
  34. package/dist/voices/index.cjs +4 -2
  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 +2 -1
  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 +6 -2
@@ -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,7 +100,8 @@ 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";
103
+ var BASE_URL = "https://api.cartesia.ai/";
104
+ var CARTESIA_VERSION = "2024-06-10";
104
105
  var constructApiUrl = (baseUrl, path, protocol) => {
105
106
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
106
107
  if (!protocol) {
@@ -125,7 +126,8 @@ var Client = class {
125
126
  const url = constructApiUrl(this.baseUrl, path);
126
127
  return (0, import_cross_fetch.default)(url.toString(), __spreadProps(__spreadValues({}, options), {
127
128
  headers: __spreadValues({
128
- "X-API-KEY": this.apiKey
129
+ "X-API-Key": this.apiKey,
130
+ "Cartesia-Version": CARTESIA_VERSION
129
131
  }, options.headers)
130
132
  }));
131
133
  }
@@ -133,7 +135,7 @@ var Client = class {
133
135
 
134
136
  // src/tts/source.ts
135
137
  var import_emittery = __toESM(require("emittery"), 1);
136
- var _emitter, _buffer, _readIndex, _closed, _sampleRate;
138
+ var _emitter, _buffer, _readIndex, _writeIndex, _closed, _sampleRate;
137
139
  var Source = class {
138
140
  /**
139
141
  * Create a new Source.
@@ -143,8 +145,9 @@ var Source = class {
143
145
  */
144
146
  constructor({ sampleRate }) {
145
147
  __privateAdd(this, _emitter, new import_emittery.default());
146
- __privateAdd(this, _buffer, new Float32Array());
148
+ __privateAdd(this, _buffer, void 0);
147
149
  __privateAdd(this, _readIndex, 0);
150
+ __privateAdd(this, _writeIndex, 0);
148
151
  __privateAdd(this, _closed, false);
149
152
  __privateAdd(this, _sampleRate, void 0);
150
153
  this.on = __privateGet(this, _emitter).on.bind(__privateGet(this, _emitter));
@@ -152,6 +155,7 @@ var Source = class {
152
155
  this.events = __privateGet(this, _emitter).events.bind(__privateGet(this, _emitter));
153
156
  this.off = __privateGet(this, _emitter).off.bind(__privateGet(this, _emitter));
154
157
  __privateSet(this, _sampleRate, sampleRate);
158
+ __privateSet(this, _buffer, new Float32Array(1024));
155
159
  }
156
160
  get sampleRate() {
157
161
  return __privateGet(this, _sampleRate);
@@ -163,7 +167,18 @@ var Source = class {
163
167
  */
164
168
  enqueue(src) {
165
169
  return __async(this, null, function* () {
166
- __privateSet(this, _buffer, new Float32Array([...__privateGet(this, _buffer), ...src]));
170
+ const requiredCapacity = __privateGet(this, _writeIndex) + src.length;
171
+ if (requiredCapacity > __privateGet(this, _buffer).length) {
172
+ let newCapacity = __privateGet(this, _buffer).length;
173
+ while (newCapacity < requiredCapacity) {
174
+ newCapacity *= 2;
175
+ }
176
+ const newBuffer = new Float32Array(newCapacity);
177
+ newBuffer.set(__privateGet(this, _buffer));
178
+ __privateSet(this, _buffer, newBuffer);
179
+ }
180
+ __privateGet(this, _buffer).set(src, __privateGet(this, _writeIndex));
181
+ __privateSet(this, _writeIndex, __privateGet(this, _writeIndex) + src.length);
167
182
  yield __privateGet(this, _emitter).emit("enqueue");
168
183
  });
169
184
  }
@@ -177,7 +192,7 @@ var Source = class {
177
192
  read(dst) {
178
193
  return __async(this, null, function* () {
179
194
  const targetReadIndex = __privateGet(this, _readIndex) + dst.length;
180
- while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _buffer).length) {
195
+ while (!__privateGet(this, _closed) && targetReadIndex > __privateGet(this, _writeIndex)) {
181
196
  yield __privateGet(this, _emitter).emit("wait");
182
197
  yield Promise.race([
183
198
  __privateGet(this, _emitter).once("enqueue"),
@@ -185,8 +200,8 @@ var Source = class {
185
200
  ]);
186
201
  yield __privateGet(this, _emitter).emit("read");
187
202
  }
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));
203
+ const read = Math.min(dst.length, __privateGet(this, _writeIndex) - __privateGet(this, _readIndex));
204
+ dst.set(__privateGet(this, _buffer).subarray(__privateGet(this, _readIndex), __privateGet(this, _readIndex) + read));
190
205
  __privateSet(this, _readIndex, __privateGet(this, _readIndex) + read);
191
206
  return read;
192
207
  });
@@ -224,19 +239,26 @@ var Source = class {
224
239
  _emitter = new WeakMap();
225
240
  _buffer = new WeakMap();
226
241
  _readIndex = new WeakMap();
242
+ _writeIndex = new WeakMap();
227
243
  _closed = new WeakMap();
228
244
  _sampleRate = new WeakMap();
229
245
 
230
246
  // src/tts/utils.ts
231
247
  var import_base64_js = __toESM(require("base64-js"), 1);
232
248
  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));
249
+ const byteArrays = filterSentinel(b64).map((b) => import_base64_js.default.toByteArray(b));
250
+ const totalLength = byteArrays.reduce(
251
+ (acc, arr) => acc + arr.length / Float32Array.BYTES_PER_ELEMENT,
252
+ 0
253
+ );
254
+ const result = new Float32Array(totalLength);
255
+ let offset = 0;
256
+ for (const arr of byteArrays) {
257
+ const floats = new Float32Array(arr.buffer);
258
+ result.set(floats, offset);
259
+ offset += floats.length;
260
+ }
261
+ return result;
240
262
  }
241
263
  function createMessageHandlerForContextId(contextId, handler) {
242
264
  return (event) => {
@@ -386,7 +408,11 @@ var WebSocket = class extends Client {
386
408
  * @throws {Error} If the WebSocket fails to connect.
387
409
  */
388
410
  connect() {
389
- const url = constructApiUrl(this.baseUrl, "/tts/websocket", "ws");
411
+ const url = constructApiUrl(
412
+ this.baseUrl,
413
+ `/tts/websocket?cartesia_version=${CARTESIA_VERSION}`,
414
+ "ws"
415
+ );
390
416
  url.searchParams.set("api_key", this.apiKey);
391
417
  const emitter = new import_emittery2.default();
392
418
  this.socket = new import_partysocket.WebSocket(url.toString());
@@ -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-3F5E46FT.js";
4
+ import "../chunk-PQ6CIPFW.js";
5
+ import "../chunk-JGP5BIUV.js";
6
+ import "../chunk-XHTDPLFR.js";
7
+ import "../chunk-RO7TY474.js";
8
8
  import "../chunk-WIFMLPT5.js";
9
9
  export {
10
10
  WebSocket as default
@@ -75,7 +75,8 @@ 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";
78
+ var BASE_URL = "https://api.cartesia.ai/";
79
+ var CARTESIA_VERSION = "2024-06-10";
79
80
  var constructApiUrl = (baseUrl, path, protocol) => {
80
81
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
81
82
  if (!protocol) {
@@ -100,7 +101,8 @@ var Client = class {
100
101
  const url = constructApiUrl(this.baseUrl, path);
101
102
  return (0, import_cross_fetch.default)(url.toString(), __spreadProps(__spreadValues({}, options), {
102
103
  headers: __spreadValues({
103
- "X-API-KEY": this.apiKey
104
+ "X-API-Key": this.apiKey,
105
+ "Cartesia-Version": CARTESIA_VERSION
104
106
  }, options.headers)
105
107
  }));
106
108
  }
@@ -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-WVTITUXX.js";
4
+ import "../chunk-JGP5BIUV.js";
5
+ import "../chunk-XHTDPLFR.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.2",
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,4 +1,5 @@
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
 
3
4
  export const constructApiUrl = (
4
5
  baseUrl: string,
@@ -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,7 +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(
154
+ this.baseUrl,
155
+ `/tts/websocket?cartesia_version=${CARTESIA_VERSION}`,
156
+ "ws",
157
+ );
154
158
  url.searchParams.set("api_key", this.apiKey);
155
159
  const emitter = new Emittery<ConnectionEventData>();
156
160
  this.socket = new PartySocketWebSocket(url.toString());