@rimelabs/sdk 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (85) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE-PRISM +201 -0
  3. package/NOTICE-PRISM +9 -0
  4. package/README.md +279 -114
  5. package/dist/auth.d.ts +9 -3
  6. package/dist/auth.js +12 -4
  7. package/dist/cancellation.d.ts +6 -0
  8. package/dist/cancellation.js +54 -0
  9. package/dist/client.d.ts +24 -23
  10. package/dist/client.js +41 -92
  11. package/dist/grpc.d.ts +27 -0
  12. package/dist/grpc.js +175 -0
  13. package/dist/index.d.ts +10 -2
  14. package/dist/index.js +6 -2
  15. package/dist/pcm.d.ts +21 -0
  16. package/dist/pcm.js +52 -0
  17. package/dist/realtime/async.d.ts +23 -0
  18. package/dist/realtime/async.js +87 -0
  19. package/dist/realtime/audio.d.ts +2 -0
  20. package/dist/realtime/audio.js +11 -0
  21. package/dist/realtime/client.d.ts +12 -0
  22. package/dist/realtime/client.js +139 -0
  23. package/dist/realtime/conversation.d.ts +47 -0
  24. package/dist/realtime/conversation.js +321 -0
  25. package/dist/realtime/faults.d.ts +2 -0
  26. package/dist/realtime/faults.js +25 -0
  27. package/dist/realtime/protocol.d.ts +99 -0
  28. package/dist/realtime/protocol.js +432 -0
  29. package/dist/realtime/session.d.ts +79 -0
  30. package/dist/realtime/session.js +808 -0
  31. package/dist/realtime/settings.d.ts +11 -0
  32. package/dist/realtime/settings.js +142 -0
  33. package/dist/realtime/transport.d.ts +16 -0
  34. package/dist/realtime/transport.js +86 -0
  35. package/dist/realtime/types.d.ts +292 -0
  36. package/dist/realtime/types.js +12 -0
  37. package/dist/stt/audio.d.ts +15 -0
  38. package/dist/stt/audio.js +46 -0
  39. package/dist/stt/client.d.ts +17 -0
  40. package/dist/stt/client.js +121 -0
  41. package/dist/stt/policy.d.ts +11 -0
  42. package/dist/stt/policy.js +11 -0
  43. package/dist/stt/protocol.d.ts +14 -0
  44. package/dist/stt/protocol.js +76 -0
  45. package/dist/stt/queue.d.ts +16 -0
  46. package/dist/stt/queue.js +45 -0
  47. package/dist/stt/stream.d.ts +48 -0
  48. package/dist/stt/stream.js +224 -0
  49. package/dist/stt/transport.d.ts +15 -0
  50. package/dist/stt/transport.js +94 -0
  51. package/dist/stt/types.d.ts +51 -0
  52. package/dist/stt/types.js +1 -0
  53. package/dist/{audio.d.ts → tts/audio.d.ts} +9 -0
  54. package/dist/{audio.js → tts/audio.js} +44 -4
  55. package/dist/tts/buffer.d.ts +3 -0
  56. package/dist/tts/buffer.js +53 -0
  57. package/dist/tts/client.d.ts +53 -0
  58. package/dist/tts/client.js +151 -0
  59. package/dist/tts/lexicon.d.ts +7 -0
  60. package/dist/tts/lexicon.js +23 -0
  61. package/dist/{policy.d.ts → tts/policy.d.ts} +2 -3
  62. package/dist/tts/policy.js +41 -0
  63. package/dist/{stream.d.ts → tts/stream.d.ts} +15 -2
  64. package/dist/{stream.js → tts/stream.js} +54 -30
  65. package/dist/tts/timestamps.d.ts +24 -0
  66. package/dist/tts/timestamps.js +46 -0
  67. package/dist/{transport.d.ts → tts/transport.d.ts} +10 -13
  68. package/dist/tts/transport.js +133 -0
  69. package/dist/validation.d.ts +5 -0
  70. package/dist/validation.js +28 -0
  71. package/docs/realtime.md +537 -0
  72. package/docs/stt.md +186 -0
  73. package/docs/tts.md +284 -0
  74. package/package.json +14 -12
  75. package/dist/policy.js +0 -82
  76. package/dist/sentences.d.ts +0 -21
  77. package/dist/sentences.js +0 -142
  78. package/dist/transport.js +0 -223
  79. package/vendor/LICENSE.blingfire +0 -21
  80. package/vendor/README.md +0 -11
  81. package/vendor/blingfire.cjs +0 -4
  82. package/vendor/blingfire.upstream.cjs +0 -1
  83. package/vendor/blingfire.wasm +0 -0
  84. /package/dist/{queue.d.ts → tts/queue.d.ts} +0 -0
  85. /package/dist/{queue.js → tts/queue.js} +0 -0
package/dist/client.js CHANGED
@@ -1,18 +1,18 @@
1
- import { AudioFormat } from "./audio.js";
2
- import { AudioStream, constructionKey } from "./stream.js";
1
+ import { STT } from "./stt/client.js";
3
2
  import { Credentials } from "./auth.js";
4
- import { policy, resolve, timeout, nonempty, abortable } from "./policy.js";
5
- import { transport, connect, metadata, discover, } from "./transport.js";
6
- import { RimeAuthenticationError, RimeInputError, RimeAudioFormatError, RimeCancelledError, RimeTimeoutError, RimeUnavailableError, } from "./errors.js";
3
+ import { RimeAuthenticationError, RimeInputError } from "./errors.js";
4
+ import { TTS, } from "./tts/client.js";
5
+ import { resolve, timeout } from "./tts/policy.js";
6
+ import { Realtime } from "./realtime/client.js";
7
7
  export class Rime {
8
- deployment;
9
8
  credentials;
10
- client = null;
9
+ ttsClient;
10
+ sttClient;
11
+ stt;
12
+ realtimeClient;
13
+ realtime;
11
14
  closed = false;
12
- streams = new Set();
13
- discoveryControllers = new Set();
14
15
  closing = null;
15
- defaultTimeout;
16
16
  tts;
17
17
  voices;
18
18
  languages;
@@ -22,99 +22,48 @@ export class Rime {
22
22
  : options.apiKey;
23
23
  if (typeof key !== "string" || !key.trim())
24
24
  throw new RimeAuthenticationError("Provide apiKey or set RIME_API_KEY");
25
- this.defaultTimeout = timeout(options.timeout);
26
- this.deployment = resolve(options.model ?? "coda", options.endpoint);
27
- this.credentials = new Credentials(key, this.deployment);
28
- this.tts = { stream: (text, options = {}) => this.stream(text, options) };
29
- this.voices = {
30
- list: async (options = {}) => {
31
- if (options.language !== undefined && options.language !== null)
32
- nonempty(options.language, "language");
33
- return this.discover("voices", options.language, options.timeout);
34
- },
25
+ const defaultTimeout = timeout(options.timeout);
26
+ const deployment = resolve(options.model ?? "coda", options.endpoint);
27
+ this.credentials = new Credentials(key, deployment);
28
+ this.sttClient = new STT(this.credentials, () => this.checkOpen());
29
+ this.stt = {
30
+ transcribe: (audio, options) => this.sttClient.transcribe(audio, options),
31
+ stream: (audio, options) => this.sttClient.stream(audio, options),
35
32
  };
36
- this.languages = {
37
- list: (options = {}) => this.discover("languages", undefined, options.timeout),
33
+ this.realtimeClient = new Realtime(this.credentials, () => this.checkOpen());
34
+ this.realtime = {
35
+ connect: (options) => this.realtimeClient.connect(options),
38
36
  };
37
+ this.ttsClient = new TTS(this.credentials, deployment, defaultTimeout, () => this.checkOpen());
38
+ this.tts = {
39
+ synthesize: (text, options) => this.ttsClient.synthesize(text, options),
40
+ synthesizeToBuffer: (text, options) => this.ttsClient.synthesizeToBuffer(text, options),
41
+ stream: (text, options) => this.ttsClient.stream(text, options),
42
+ };
43
+ this.voices = this.ttsClient.voices;
44
+ this.languages = this.ttsClient.languages;
39
45
  }
40
46
  checkOpen() {
41
47
  if (this.closed)
42
48
  throw new RimeInputError("The Rime client is closed");
43
49
  }
44
- async prepare(signal) {
45
- this.checkOpen();
46
- const auth = await this.credentials.metadata(signal);
47
- this.checkOpen();
48
- this.client ??= transport.makeClient(this.deployment.target);
49
- await connect(this.client, signal);
50
- return { client: this.client, metadata: metadata(auth) };
51
- }
52
- stream(text, options) {
53
- this.checkOpen();
54
- if (typeof text === "string") {
55
- if (!text.trim())
56
- throw new RimeInputError("Text must contain non-whitespace characters");
57
- }
58
- else if (!text || typeof text[Symbol.asyncIterator] !== "function")
59
- throw new RimeInputError("text must be a string or an async iterable of strings");
60
- const voice = options.voice === undefined || options.voice === null
61
- ? this.deployment.defaultVoice
62
- : nonempty(options.voice, "voice");
63
- const language = nonempty(options.language ?? "en", "language");
64
- const format = options.audioFormat ?? AudioFormat.PCM_24000;
65
- if (format !== AudioFormat.PCM_24000 && format !== AudioFormat.MULAW_8000)
66
- throw new RimeAudioFormatError("Select a named AudioFormat profile");
67
- const audio = new AudioStream(constructionKey, {
68
- prepare: (s) => this.prepare(s),
69
- checkOpen: () => this.checkOpen(),
70
- forget: (s) => this.streams.delete(s),
71
- }, text, voice, language, format, timeout(options.timeout, this.defaultTimeout));
72
- this.streams.add(audio);
73
- return audio;
74
- }
75
- async discover(kind, language, override) {
76
- this.checkOpen();
77
- const budget = Math.min(timeout(override, this.defaultTimeout) ?? Infinity, policy.discoveryTimeout);
78
- const controller = new AbortController();
79
- let requestId = null;
80
- this.discoveryControllers.add(controller);
81
- const timer = setTimeout(() => controller.abort(new RimeTimeoutError("Discovery deadline expired")), budget * 1000);
82
- try {
83
- const prepared = await this.prepare(controller.signal);
84
- for (let attempt = 0;; attempt++) {
85
- requestId = null;
86
- try {
87
- return await discover(prepared, kind, language, controller.signal);
88
- }
89
- catch (error) {
90
- if (!(error instanceof RimeUnavailableError) || attempt === 2)
91
- throw error;
92
- requestId = error.requestId;
93
- await abortable(new Promise((resolve) => setTimeout(resolve, 50 * 2 ** attempt)), controller.signal);
94
- }
95
- }
96
- }
97
- catch (error) {
98
- if (error instanceof RimeTimeoutError && requestId !== null)
99
- throw new RimeTimeoutError(error.message, requestId, {
100
- cause: error.cause,
101
- });
102
- throw error;
103
- }
104
- finally {
105
- clearTimeout(timer);
106
- this.discoveryControllers.delete(controller);
107
- }
108
- }
109
50
  async close() {
110
51
  if (!this.closing) {
111
52
  this.closed = true;
112
53
  this.closing = (async () => {
113
- for (const controller of this.discoveryControllers)
114
- controller.abort(new RimeCancelledError("Client closed during discovery"));
115
- await Promise.all([...this.streams].map((stream) => stream.cancel()));
116
- await this.credentials.close();
117
- this.client?.close();
54
+ try {
55
+ const results = await Promise.allSettled([
56
+ this.ttsClient.close(),
57
+ this.sttClient.close(),
58
+ this.realtimeClient.close(),
59
+ ]);
60
+ for (const result of results)
61
+ if (result.status === "rejected")
62
+ throw result.reason;
63
+ }
64
+ finally {
65
+ await this.credentials.close();
66
+ }
118
67
  })();
119
68
  }
120
69
  await this.closing;
package/dist/grpc.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import * as grpc from "@grpc/grpc-js";
2
+ import { type DescMessage, type MessageShape } from "@bufbuild/protobuf";
3
+ import * as errors from "./errors.js";
4
+ export declare function rpcError(code: number, requestId?: string | null, details?: string): errors.RimeError;
5
+ export declare function serializer<D extends DescMessage>(desc: D): (message: MessageShape<D>) => Buffer<ArrayBuffer>;
6
+ export declare function deserializer<D extends DescMessage>(desc: D): (data: Buffer) => MessageShape<D>;
7
+ export declare function requestId(metadata?: grpc.Metadata): string | null;
8
+ export declare function nativeError(error: unknown, id: string | null): errors.RimeError;
9
+ export declare class BidiCall<Input, Output> {
10
+ private readonly call;
11
+ private signal;
12
+ private readonly metadataPromise;
13
+ private readonly statusPromise;
14
+ private status;
15
+ private id;
16
+ constructor(call: grpc.ClientDuplexStream<Input, Output>, signal: AbortSignal);
17
+ get requestId(): string | null;
18
+ get done(): boolean;
19
+ cancel(): void;
20
+ private error;
21
+ headers(): Promise<grpc.Metadata>;
22
+ responses(): AsyncGenerator<Output>;
23
+ write(message: Input): Promise<void>;
24
+ finishInput(): void;
25
+ }
26
+ export declare function connect(client: grpc.Client, signal: AbortSignal, seconds: number): Promise<void>;
27
+ export declare function metadata(value: string): grpc.Metadata;
package/dist/grpc.js ADDED
@@ -0,0 +1,175 @@
1
+ import { abortable } from "./cancellation.js";
2
+ import * as grpc from "@grpc/grpc-js";
3
+ import { fromBinary, toBinary, } from "@bufbuild/protobuf";
4
+ import * as errors from "./errors.js";
5
+ export function rpcError(code, requestId = null, details) {
6
+ const classes = {
7
+ [grpc.status.UNAUTHENTICATED]: errors.RimeAuthenticationError,
8
+ [grpc.status.PERMISSION_DENIED]: errors.RimePermissionError,
9
+ [grpc.status.INVALID_ARGUMENT]: errors.RimeInputError,
10
+ [grpc.status.RESOURCE_EXHAUSTED]: errors.RimeResourceLimitError,
11
+ [grpc.status.UNAVAILABLE]: errors.RimeUnavailableError,
12
+ [grpc.status.DEADLINE_EXCEEDED]: errors.RimeTimeoutError,
13
+ [grpc.status.CANCELLED]: errors.RimeCancelledError,
14
+ };
15
+ const ErrorType = classes[code] ?? errors.RimeStreamError;
16
+ return new ErrorType(details?.trim()
17
+ ? details
18
+ : `Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
19
+ }
20
+ export function serializer(desc) {
21
+ return (message) => Buffer.from(toBinary(desc, message));
22
+ }
23
+ export function deserializer(desc) {
24
+ return (data) => fromBinary(desc, data);
25
+ }
26
+ export function requestId(metadata) {
27
+ return metadata?.get("x-request-id")[0]?.toString() ?? null;
28
+ }
29
+ export function nativeError(error, id) {
30
+ if (error instanceof errors.RimeError)
31
+ return error;
32
+ if (error &&
33
+ typeof error === "object" &&
34
+ "code" in error &&
35
+ typeof error.code === "number") {
36
+ const metadata = "metadata" in error ? error.metadata : null;
37
+ return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null), "details" in error && typeof error.details === "string"
38
+ ? error.details
39
+ : undefined);
40
+ }
41
+ return new errors.RimeStreamError("Rime transport failed", id, {
42
+ cause: error,
43
+ });
44
+ }
45
+ export class BidiCall {
46
+ call;
47
+ signal;
48
+ metadataPromise;
49
+ statusPromise;
50
+ status = null;
51
+ id = null;
52
+ constructor(call, signal) {
53
+ this.call = call;
54
+ this.signal = signal;
55
+ this.metadataPromise = new Promise((resolve, reject) => {
56
+ this.call.once("metadata", (metadata) => {
57
+ this.id = requestId(metadata);
58
+ resolve(metadata);
59
+ });
60
+ // A trailers-only response has no metadata event.
61
+ this.call.once("status", () => resolve(new grpc.Metadata()));
62
+ this.call.once("error", reject);
63
+ });
64
+ this.metadataPromise.catch(() => { });
65
+ this.statusPromise = new Promise((resolve) => {
66
+ this.call.once("status", (status) => {
67
+ this.status = status;
68
+ this.id ??= requestId(status.metadata);
69
+ resolve(status);
70
+ });
71
+ });
72
+ this.call.on("error", () => { });
73
+ }
74
+ get requestId() {
75
+ return this.id;
76
+ }
77
+ get done() {
78
+ return this.status !== null || this.call.destroyed;
79
+ }
80
+ cancel() {
81
+ this.call.cancel();
82
+ }
83
+ error(error) {
84
+ const failure = nativeError(error, this.id);
85
+ this.id ??= failure.requestId;
86
+ return failure;
87
+ }
88
+ async headers() {
89
+ try {
90
+ return await abortable(this.metadataPromise, this.signal);
91
+ }
92
+ catch (error) {
93
+ if (this.signal.aborted)
94
+ throw this.signal.reason;
95
+ throw this.error(error);
96
+ }
97
+ }
98
+ async *responses() {
99
+ try {
100
+ await this.headers();
101
+ const responses = this.call;
102
+ for await (const response of responses)
103
+ yield response;
104
+ const status = await abortable(this.statusPromise, this.signal);
105
+ if (status.code !== grpc.status.OK)
106
+ throw rpcError(status.code, this.id, status.details);
107
+ }
108
+ catch (error) {
109
+ if (this.signal.aborted)
110
+ throw this.signal.reason;
111
+ throw this.error(error);
112
+ }
113
+ }
114
+ async write(message) {
115
+ try {
116
+ this.signal.throwIfAborted();
117
+ if (this.status) {
118
+ if (this.status.code !== grpc.status.OK)
119
+ throw rpcError(this.status.code, this.id, this.status.details);
120
+ throw new errors.RimeStreamError("The service completed before input finished", this.id);
121
+ }
122
+ await abortable(new Promise((resolve, reject) => {
123
+ const finish = (error) => {
124
+ this.call.removeListener("status", onStatus);
125
+ if (error)
126
+ reject(error);
127
+ else
128
+ resolve();
129
+ };
130
+ // A rejected native write can emit final status without invoking its
131
+ // callback (for example, when exceeding the send-message limit).
132
+ const onStatus = (status) => finish(status.code === grpc.status.OK
133
+ ? new errors.RimeStreamError("The service completed before input finished", this.id)
134
+ : rpcError(status.code, this.id, status.details));
135
+ this.call.once("status", onStatus);
136
+ try {
137
+ this.call.write(message, finish);
138
+ }
139
+ catch (error) {
140
+ finish(error);
141
+ }
142
+ }), this.signal);
143
+ }
144
+ catch (error) {
145
+ if (this.signal.aborted)
146
+ throw this.signal.reason;
147
+ if (error instanceof errors.RimeError)
148
+ throw error;
149
+ // A native write error can precede the final status event. Preserve the
150
+ // server rejection even if the reader has not consumed a response.
151
+ const status = await abortable(this.statusPromise, this.signal);
152
+ if (status.code !== grpc.status.OK)
153
+ throw rpcError(status.code, this.id, status.details);
154
+ throw this.error(error);
155
+ }
156
+ }
157
+ finishInput() {
158
+ try {
159
+ this.call.end();
160
+ }
161
+ catch (error) {
162
+ throw this.error(error);
163
+ }
164
+ }
165
+ }
166
+ export async function connect(client, signal, seconds) {
167
+ await abortable(new Promise((resolve, reject) => client.waitForReady(Date.now() + seconds * 1000, (error) => error
168
+ ? reject(new errors.RimeTimeoutError("Connection establishment timed out"))
169
+ : resolve())), signal);
170
+ }
171
+ export function metadata(value) {
172
+ const result = new grpc.Metadata();
173
+ result.set("authorization", value);
174
+ return result;
175
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,12 @@
1
1
  export { Rime, type RimeOptions, type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions, } from "./client.js";
2
- export { AudioStream } from "./stream.js";
3
- export { AudioFormat } from "./audio.js";
2
+ export { AudioStream } from "./tts/stream.js";
3
+ export { AudioFormat } from "./tts/audio.js";
4
+ export type { PronunciationEntry } from "./tts/lexicon.js";
5
+ export type { TimestampResult, TimestampStatus, WordTimestamp, } from "./tts/timestamps.js";
4
6
  export * from "./errors.js";
7
+ export { RealtimeSession } from "./realtime/session.js";
8
+ export * from "./realtime/types.js";
9
+ export { TranscriptStream } from "./stt/stream.js";
10
+ export type { AudioSource, TranscribeOptions, Transcription, TranscriptionOptions, TranscriptionMode, TranscriptionPartial, TranscriptionDelta, TranscriptionOutputContract, TranscriptionFinal, TranscriptionUpdate, } from "./stt/types.js";
11
+ export { runConversation } from "./realtime/conversation.js";
12
+ export type { ConversationSource, ConversationOutput, ConversationPlayback, ConversationOptions, } from "./realtime/conversation.js";
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export { Rime, } from "./client.js";
2
- export { AudioStream } from "./stream.js";
3
- export { AudioFormat } from "./audio.js";
2
+ export { AudioStream } from "./tts/stream.js";
3
+ export { AudioFormat } from "./tts/audio.js";
4
4
  export * from "./errors.js";
5
+ export { RealtimeSession } from "./realtime/session.js";
6
+ export * from "./realtime/types.js";
7
+ export { TranscriptStream } from "./stt/stream.js";
8
+ export { runConversation } from "./realtime/conversation.js";
package/dist/pcm.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** Headerless signed little-endian PCM16; stereo samples are interleaved. */
2
+ export interface PCMFormat {
3
+ /** Input frames per second; omitted means 16000. */
4
+ readonly sampleRate?: 8000 | 16000 | 24000 | 48000;
5
+ /** Samples per frame; omitted means one (mono). */
6
+ readonly channels?: 1 | 2;
7
+ /** Two-byte signed samples with no file header. */
8
+ readonly encoding?: "pcm_s16le";
9
+ }
10
+ export declare function resolvePCMFormat(input?: PCMFormat): Required<PCMFormat>;
11
+ /** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
12
+ * Integer phase remains bounded and preserves samples across chunk boundaries.
13
+ */
14
+ export declare class InputConverter {
15
+ readonly format: Required<PCMFormat>;
16
+ private phase;
17
+ private previous;
18
+ constructor(format: Required<PCMFormat>);
19
+ clone(): InputConverter;
20
+ process(input: Uint8Array): Buffer;
21
+ }
package/dist/pcm.js ADDED
@@ -0,0 +1,52 @@
1
+ import { RimeAudioFormatError } from "./errors.js";
2
+ export function resolvePCMFormat(input) {
3
+ if (input !== undefined &&
4
+ (input === null || typeof input !== "object" || Array.isArray(input)))
5
+ throw new RimeAudioFormatError("Input format must be a PCM format object");
6
+ const format = {
7
+ sampleRate: input?.sampleRate ?? 16000,
8
+ channels: input?.channels ?? 1,
9
+ encoding: input?.encoding ?? "pcm_s16le",
10
+ };
11
+ if (![8000, 16000, 24000, 48000].includes(format.sampleRate) ||
12
+ ![1, 2].includes(format.channels) ||
13
+ format.encoding !== "pcm_s16le")
14
+ throw new RimeAudioFormatError("Use PCM16 at 8, 16, 24 or 48 kHz, with one or two channels");
15
+ return format;
16
+ }
17
+ /** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
18
+ * Integer phase remains bounded and preserves samples across chunk boundaries.
19
+ */
20
+ export class InputConverter {
21
+ format;
22
+ phase = -16000;
23
+ previous = 0;
24
+ constructor(format) {
25
+ this.format = format;
26
+ }
27
+ clone() {
28
+ const copy = new InputConverter(this.format);
29
+ copy.phase = this.phase;
30
+ copy.previous = this.previous;
31
+ return copy;
32
+ }
33
+ process(input) {
34
+ const data = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
35
+ const samples = [];
36
+ for (let offset = 0; offset < data.length; offset += this.format.channels * 2) {
37
+ const current = this.format.channels === 1
38
+ ? data.readInt16LE(offset)
39
+ : Math.floor((data.readInt16LE(offset) + data.readInt16LE(offset + 2)) / 2);
40
+ this.phase += 16000;
41
+ while (this.phase >= 0) {
42
+ samples.push(Math.floor((this.previous * this.phase + current * (16000 - this.phase)) /
43
+ 16000));
44
+ this.phase -= this.format.sampleRate;
45
+ }
46
+ this.previous = current;
47
+ }
48
+ const output = Buffer.alloc(samples.length * 2);
49
+ samples.forEach((sample, index) => output.writeInt16LE(sample, index * 2));
50
+ return output;
51
+ }
52
+ }
@@ -0,0 +1,23 @@
1
+ export { scope } from "../cancellation.js";
2
+ export declare class Deferred<T> {
3
+ readonly promise: Promise<T>;
4
+ resolve: (value: T) => void;
5
+ reject: (error: unknown) => void;
6
+ constructor();
7
+ }
8
+ export declare class Flag {
9
+ private waiters;
10
+ isSet: boolean;
11
+ set(): void;
12
+ clear(): void;
13
+ wait(signal: AbortSignal): Promise<void>;
14
+ }
15
+ export declare class Mutex {
16
+ private locked;
17
+ private waiters;
18
+ acquire(signal: AbortSignal): Promise<() => void>;
19
+ }
20
+ export declare function cancellation(signal?: AbortSignal): {
21
+ signal: AbortSignal;
22
+ dispose: () => void | undefined;
23
+ };
@@ -0,0 +1,87 @@
1
+ export { scope } from "../cancellation.js";
2
+ import { abortable } from "../cancellation.js";
3
+ import { RimeCancelledError } from "../errors.js";
4
+ export class Deferred {
5
+ promise;
6
+ resolve;
7
+ reject;
8
+ constructor() {
9
+ this.promise = new Promise((resolve, reject) => {
10
+ this.resolve = resolve;
11
+ this.reject = reject;
12
+ });
13
+ // Session failures can arrive before a caller starts waiting.
14
+ this.promise.catch(() => { });
15
+ }
16
+ }
17
+ export class Flag {
18
+ waiters = new Set();
19
+ isSet = false;
20
+ set() {
21
+ this.isSet = true;
22
+ for (const wake of this.waiters)
23
+ wake();
24
+ }
25
+ clear() {
26
+ this.isSet = false;
27
+ }
28
+ wait(signal) {
29
+ if (signal.aborted)
30
+ return Promise.reject(signal.reason);
31
+ if (this.isSet)
32
+ return Promise.resolve();
33
+ return new Promise((resolve, reject) => {
34
+ const cleanup = () => {
35
+ this.waiters.delete(wake);
36
+ signal.removeEventListener("abort", abort);
37
+ };
38
+ const wake = () => {
39
+ cleanup();
40
+ resolve();
41
+ };
42
+ const abort = () => {
43
+ cleanup();
44
+ reject(signal.reason);
45
+ };
46
+ this.waiters.add(wake);
47
+ signal.addEventListener("abort", abort, { once: true });
48
+ });
49
+ }
50
+ }
51
+ export class Mutex {
52
+ locked = false;
53
+ waiters = new Set();
54
+ async acquire(signal) {
55
+ signal.throwIfAborted();
56
+ while (this.locked) {
57
+ const wake = new Deferred();
58
+ const notify = () => wake.resolve();
59
+ this.waiters.add(notify);
60
+ try {
61
+ await abortable(wake.promise, signal);
62
+ }
63
+ finally {
64
+ this.waiters.delete(notify);
65
+ }
66
+ signal.throwIfAborted();
67
+ }
68
+ this.locked = true;
69
+ return () => {
70
+ this.locked = false;
71
+ for (const wake of this.waiters)
72
+ wake();
73
+ };
74
+ }
75
+ }
76
+ export function cancellation(signal) {
77
+ const controller = new AbortController();
78
+ const abort = () => controller.abort(new RimeCancelledError("Realtime operation cancelled"));
79
+ if (signal?.aborted)
80
+ abort();
81
+ else
82
+ signal?.addEventListener("abort", abort, { once: true });
83
+ return {
84
+ signal: controller.signal,
85
+ dispose: () => signal?.removeEventListener("abort", abort),
86
+ };
87
+ }
@@ -0,0 +1,2 @@
1
+ import type { AudioChunk, PCMFormat } from "./types.js";
2
+ export declare function formatOf(chunk: AudioChunk): Required<PCMFormat>;
@@ -0,0 +1,11 @@
1
+ import { RimeAudioFormatError } from "../errors.js";
2
+ import { resolvePCMFormat } from "../pcm.js";
3
+ export function formatOf(chunk) {
4
+ const format = resolvePCMFormat(chunk.format);
5
+ if (!(chunk.data instanceof Uint8Array) ||
6
+ chunk.data.length % (2 * format.channels))
7
+ throw new RimeAudioFormatError("Audio must contain complete PCM16 frames as Uint8Array");
8
+ if (chunk.data.length > 192000)
9
+ throw new RimeAudioFormatError("Send audio in chunks of at most 192000 bytes");
10
+ return format;
11
+ }
@@ -0,0 +1,12 @@
1
+ import { Credentials } from "../auth.js";
2
+ import { RealtimeSession } from "./session.js";
3
+ import type { RealtimeConnectOptions } from "./types.js";
4
+ export declare class Realtime {
5
+ private readonly credentials;
6
+ private readonly checkOpen;
7
+ private readonly sessions;
8
+ private readonly opening;
9
+ constructor(credentials: Credentials, checkOpen: () => void);
10
+ connect(options?: RealtimeConnectOptions): Promise<RealtimeSession>;
11
+ close(): Promise<void>;
12
+ }