@liforma/client 0.3.0 → 0.4.1

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 (179) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +14 -1
  3. package/dist/byo/audioCapture.d.ts +24 -0
  4. package/dist/byo/audioCapture.d.ts.map +1 -0
  5. package/dist/byo/index.d.ts +8 -0
  6. package/dist/byo/index.d.ts.map +1 -0
  7. package/dist/byo/index.js +307 -0
  8. package/dist/byo/index.js.map +1 -0
  9. package/dist/byo/mic.d.ts +27 -0
  10. package/dist/byo/mic.d.ts.map +1 -0
  11. package/dist/byo/pcm.d.ts +33 -0
  12. package/dist/byo/pcm.d.ts.map +1 -0
  13. package/dist/byo/pcmCarry.d.ts +17 -0
  14. package/dist/byo/pcmCarry.d.ts.map +1 -0
  15. package/dist/byo/resample.d.ts +18 -0
  16. package/dist/byo/resample.d.ts.map +1 -0
  17. package/dist/byo/turn.d.ts +39 -0
  18. package/dist/byo/turn.d.ts.map +1 -0
  19. package/dist/byo/types.d.ts +74 -0
  20. package/dist/byo/types.d.ts.map +1 -0
  21. package/dist/deepgram/connectDeepgramAgent.d.ts +51 -0
  22. package/dist/deepgram/connectDeepgramAgent.d.ts.map +1 -0
  23. package/dist/deepgram/deepgramAgentMessages.d.ts +31 -0
  24. package/dist/deepgram/deepgramAgentMessages.d.ts.map +1 -0
  25. package/dist/deepgram/index.d.ts +4 -0
  26. package/dist/deepgram/index.d.ts.map +1 -0
  27. package/dist/deepgram/index.js +142 -0
  28. package/dist/deepgram/index.js.map +1 -0
  29. package/dist/elevenlabs/connectElevenLabsAgent.d.ts +23 -0
  30. package/dist/elevenlabs/connectElevenLabsAgent.d.ts.map +1 -0
  31. package/dist/elevenlabs/index.d.ts +4 -0
  32. package/dist/elevenlabs/index.d.ts.map +1 -0
  33. package/dist/elevenlabs/index.js +89 -0
  34. package/dist/elevenlabs/index.js.map +1 -0
  35. package/dist/google/connectGeminiLive.d.ts +34 -0
  36. package/dist/google/connectGeminiLive.d.ts.map +1 -0
  37. package/dist/google/index.d.ts +4 -0
  38. package/dist/google/index.d.ts.map +1 -0
  39. package/dist/google/index.js +98 -0
  40. package/dist/google/index.js.map +1 -0
  41. package/dist/livekit/connectLiveKitAgent.d.ts +46 -0
  42. package/dist/livekit/connectLiveKitAgent.d.ts.map +1 -0
  43. package/dist/livekit/index.d.ts +3 -0
  44. package/dist/livekit/index.d.ts.map +1 -0
  45. package/dist/livekit/index.js +113 -0
  46. package/dist/livekit/index.js.map +1 -0
  47. package/dist/next/byo/audioCapture.d.ts +24 -0
  48. package/dist/next/byo/audioCapture.d.ts.map +1 -0
  49. package/dist/next/byo/pcm.d.ts +33 -0
  50. package/dist/next/byo/pcm.d.ts.map +1 -0
  51. package/dist/next/byo/resample.d.ts +18 -0
  52. package/dist/next/byo/resample.d.ts.map +1 -0
  53. package/dist/next/index.js +1135 -4183
  54. package/dist/next/index.js.map +1 -1
  55. package/dist/next/next/createLiformaSessionRouteHandler.d.ts +3 -1
  56. package/dist/next/next/createLiformaSessionRouteHandler.d.ts.map +1 -1
  57. package/dist/next/v2/api/mintPublicSession.d.ts +21 -15
  58. package/dist/next/v2/api/mintPublicSession.d.ts.map +1 -1
  59. package/dist/next/v2/api/sessionEndpointRequest.d.ts +8 -3
  60. package/dist/next/v2/api/sessionEndpointRequest.d.ts.map +1 -1
  61. package/dist/next/v2/embed/iframeHost.d.ts +24 -1
  62. package/dist/next/v2/embed/iframeHost.d.ts.map +1 -1
  63. package/dist/next/v2/embed/protocol.d.ts +61 -1
  64. package/dist/next/v2/embed/protocol.d.ts.map +1 -1
  65. package/dist/next/v2/experience.d.ts +66 -46
  66. package/dist/next/v2/experience.d.ts.map +1 -1
  67. package/dist/next/v2/experienceEvents.d.ts +3 -0
  68. package/dist/next/v2/experienceEvents.d.ts.map +1 -1
  69. package/dist/next/v2/speech/captureMediaStreamTrack.d.ts +1 -1
  70. package/dist/next/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
  71. package/dist/next/v2/speech/speechController.d.ts.map +1 -1
  72. package/dist/next/v2/speech/types.d.ts +13 -0
  73. package/dist/next/v2/speech/types.d.ts.map +1 -1
  74. package/dist/next/v2/types/sessionLaunch.d.ts +43 -0
  75. package/dist/next/v2/types/sessionLaunch.d.ts.map +1 -0
  76. package/dist/next/widget/mountExperienceWidget.d.ts.map +1 -1
  77. package/dist/next/widget/styles.d.ts +1 -1
  78. package/dist/next/widget/styles.d.ts.map +1 -1
  79. package/dist/next/widget/types.d.ts +6 -1
  80. package/dist/next/widget/types.d.ts.map +1 -1
  81. package/dist/npm/byo/audioCapture.d.ts +24 -0
  82. package/dist/npm/byo/audioCapture.d.ts.map +1 -0
  83. package/dist/npm/byo/pcm.d.ts +33 -0
  84. package/dist/npm/byo/pcm.d.ts.map +1 -0
  85. package/dist/npm/byo/resample.d.ts +18 -0
  86. package/dist/npm/byo/resample.d.ts.map +1 -0
  87. package/dist/npm/index.js +960 -4011
  88. package/dist/npm/index.js.map +1 -1
  89. package/dist/npm/v2/api/mintPublicSession.d.ts +21 -15
  90. package/dist/npm/v2/api/mintPublicSession.d.ts.map +1 -1
  91. package/dist/npm/v2/api/sessionEndpointRequest.d.ts +8 -3
  92. package/dist/npm/v2/api/sessionEndpointRequest.d.ts.map +1 -1
  93. package/dist/npm/v2/embed/iframeHost.d.ts +24 -1
  94. package/dist/npm/v2/embed/iframeHost.d.ts.map +1 -1
  95. package/dist/npm/v2/embed/protocol.d.ts +61 -1
  96. package/dist/npm/v2/embed/protocol.d.ts.map +1 -1
  97. package/dist/npm/v2/experience.d.ts +66 -46
  98. package/dist/npm/v2/experience.d.ts.map +1 -1
  99. package/dist/npm/v2/experienceEvents.d.ts +3 -0
  100. package/dist/npm/v2/experienceEvents.d.ts.map +1 -1
  101. package/dist/npm/v2/index.d.ts +1 -0
  102. package/dist/npm/v2/index.d.ts.map +1 -1
  103. package/dist/npm/v2/liforma-experience.d.ts.map +1 -1
  104. package/dist/npm/v2/npm.d.ts +1 -0
  105. package/dist/npm/v2/npm.d.ts.map +1 -1
  106. package/dist/npm/v2/speech/captureMediaStreamTrack.d.ts +1 -1
  107. package/dist/npm/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
  108. package/dist/npm/v2/speech/speechController.d.ts.map +1 -1
  109. package/dist/npm/v2/speech/types.d.ts +13 -0
  110. package/dist/npm/v2/speech/types.d.ts.map +1 -1
  111. package/dist/npm/v2/types/index.d.ts +2 -0
  112. package/dist/npm/v2/types/index.d.ts.map +1 -1
  113. package/dist/npm/v2/types/sessionLaunch.d.ts +43 -0
  114. package/dist/npm/v2/types/sessionLaunch.d.ts.map +1 -0
  115. package/dist/npm/widget/mountExperienceWidget.d.ts.map +1 -1
  116. package/dist/npm/widget/styles.d.ts +1 -1
  117. package/dist/npm/widget/styles.d.ts.map +1 -1
  118. package/dist/npm/widget/types.d.ts +6 -1
  119. package/dist/npm/widget/types.d.ts.map +1 -1
  120. package/dist/openai/connectOpenAiRealtime.d.ts +41 -0
  121. package/dist/openai/connectOpenAiRealtime.d.ts.map +1 -0
  122. package/dist/openai/connectOpenAiRealtimeWebRtc.d.ts +37 -0
  123. package/dist/openai/connectOpenAiRealtimeWebRtc.d.ts.map +1 -0
  124. package/dist/openai/index.d.ts +5 -0
  125. package/dist/openai/index.d.ts.map +1 -0
  126. package/dist/openai/index.js +282 -0
  127. package/dist/openai/index.js.map +1 -0
  128. package/dist/react/byo/audioCapture.d.ts +24 -0
  129. package/dist/react/byo/audioCapture.d.ts.map +1 -0
  130. package/dist/react/byo/pcm.d.ts +33 -0
  131. package/dist/react/byo/pcm.d.ts.map +1 -0
  132. package/dist/react/byo/resample.d.ts +18 -0
  133. package/dist/react/byo/resample.d.ts.map +1 -0
  134. package/dist/react/component/experienceComponentLaunch.d.ts +1 -0
  135. package/dist/react/component/experienceComponentLaunch.d.ts.map +1 -1
  136. package/dist/react/component/experienceComponentRuntime.d.ts +11 -1
  137. package/dist/react/component/experienceComponentRuntime.d.ts.map +1 -1
  138. package/dist/react/component/experienceComponentTypes.d.ts +25 -10
  139. package/dist/react/component/experienceComponentTypes.d.ts.map +1 -1
  140. package/dist/react/index.js +1291 -4327
  141. package/dist/react/index.js.map +1 -1
  142. package/dist/react/react/Experience.d.ts.map +1 -1
  143. package/dist/react/v2/api/mintPublicSession.d.ts +21 -15
  144. package/dist/react/v2/api/mintPublicSession.d.ts.map +1 -1
  145. package/dist/react/v2/api/sessionEndpointRequest.d.ts +8 -3
  146. package/dist/react/v2/api/sessionEndpointRequest.d.ts.map +1 -1
  147. package/dist/react/v2/embed/iframeHost.d.ts +24 -1
  148. package/dist/react/v2/embed/iframeHost.d.ts.map +1 -1
  149. package/dist/react/v2/embed/protocol.d.ts +61 -1
  150. package/dist/react/v2/embed/protocol.d.ts.map +1 -1
  151. package/dist/react/v2/experience.d.ts +66 -46
  152. package/dist/react/v2/experience.d.ts.map +1 -1
  153. package/dist/react/v2/experienceEvents.d.ts +3 -0
  154. package/dist/react/v2/experienceEvents.d.ts.map +1 -1
  155. package/dist/react/v2/speech/captureMediaStreamTrack.d.ts +1 -1
  156. package/dist/react/v2/speech/captureMediaStreamTrack.d.ts.map +1 -1
  157. package/dist/react/v2/speech/speechController.d.ts.map +1 -1
  158. package/dist/react/v2/speech/types.d.ts +13 -0
  159. package/dist/react/v2/speech/types.d.ts.map +1 -1
  160. package/dist/react/v2/types/sessionLaunch.d.ts +43 -0
  161. package/dist/react/v2/types/sessionLaunch.d.ts.map +1 -0
  162. package/dist/react/widget/mountExperienceWidget.d.ts.map +1 -1
  163. package/dist/react/widget/styles.d.ts +1 -1
  164. package/dist/react/widget/styles.d.ts.map +1 -1
  165. package/dist/react/widget/types.d.ts +6 -1
  166. package/dist/react/widget/types.d.ts.map +1 -1
  167. package/dist/sdk/v2/client.js +33 -69
  168. package/dist/sdk/v2/client.js.map +1 -1
  169. package/dist/svelte/Experience.svelte +54 -38
  170. package/dist/svelte/Experience.svelte.d.ts +2 -1
  171. package/package.json +55 -7
  172. package/dist/next/v2/speechOnlyManifest.d.ts +0 -7
  173. package/dist/next/v2/speechOnlyManifest.d.ts.map +0 -1
  174. package/dist/next/v2/types/sessionManifestSchema.d.ts +0 -183
  175. package/dist/next/v2/types/sessionManifestSchema.d.ts.map +0 -1
  176. package/dist/react/v2/speechOnlyManifest.d.ts +0 -7
  177. package/dist/react/v2/speechOnlyManifest.d.ts.map +0 -1
  178. package/dist/react/v2/types/sessionManifestSchema.d.ts +0 -183
  179. package/dist/react/v2/types/sessionManifestSchema.d.ts.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @liforma/client changelog
2
2
 
3
+ ## 0.4.1 — 2026-08-11
4
+
5
+ ### Added
6
+
7
+ - `learningLocale` / `secondaryLocale` on `<Experience>`, React launch options, and `<liforma-experience learning-locale>` / `secondary-locale` / `locale` attributes
8
+ - Browser mint defaults `locale` from `navigator.language` when omitted
9
+
10
+ ### Fixed
11
+
12
+ - React `buildExperienceStartOptions` / `experienceLaunchKey` now pass and remint on learning/secondary locale
13
+
14
+ ## 0.4.0 — 2026-08-11
15
+
16
+ ### Added
17
+
18
+ - First-class BYO voice helpers:
19
+ - `@liforma/client/elevenlabs` — `connectElevenLabsAgent`
20
+ - `@liforma/client/openai` — `connectOpenAiRealtime` / `connectOpenAiRealtimeWebRtc`
21
+ - `@liforma/client/deepgram` — `connectDeepgramAgent`
22
+ - `@liforma/client/google` — `connectGeminiLive`
23
+ - `@liforma/client/livekit` — `connectLiveKitAgent` (`createUtterance({ track })` + optional transcript)
24
+ - Shared `@liforma/client/byo` primitives (`createTurnSession`, PCM/mic helpers, `BYO_PLAYBACK_GUIDANCE`)
25
+ - Optional peers: `@elevenlabs/client`, `livekit-client`
26
+
27
+ ### Notes
28
+
29
+ - Prefer these helpers over hand-rolled vendor WebSockets; see docs → Bring your own voice
30
+
3
31
  ## 0.3.0 — 2026-08-06
4
32
 
5
33
  ### Added
package/README.md CHANGED
@@ -16,7 +16,7 @@ Portal (API keys & origins): [app.liforma.ai](https://app.liforma.ai)
16
16
  npm install @liforma/client
17
17
  ```
18
18
 
19
- Optional peers: `svelte` ^5 for the Svelte entry; `react` / `react-dom` ^18 or ^19 for React / Next.
19
+ Optional peers: `svelte` ^5 for the Svelte entry; `react` / `react-dom` ^18 or ^19 for React / Next; `@elevenlabs/client` for `/elevenlabs`; `livekit-client` for `/livekit`. `/openai`, `/deepgram`, and `/google` use the browser WebSocket API (no extra peer). Shared BYO primitives live in `@liforma/client/byo` (used by the vendor helpers; rarely imported directly).
20
20
 
21
21
  ## Hello world
22
22
 
@@ -81,6 +81,12 @@ export function Demo() {
81
81
  | `@liforma/client/svelte` | `<Experience />`, `<ExperienceThumbnail />`, `<ExperienceWidget />` |
82
82
  | `@liforma/client/react` | React components |
83
83
  | `@liforma/client/next` | Next helpers + client components |
84
+ | `@liforma/client/byo` | Shared BYO primitives (PCM/mic/turn helpers) |
85
+ | `@liforma/client/elevenlabs` | `connectElevenLabsAgent` (BYO ElevenLabs Agents → avatar) |
86
+ | `@liforma/client/openai` | `connectOpenAiRealtime` / `connectOpenAiRealtimeWebRtc` (BYO OpenAI Realtime → avatar) |
87
+ | `@liforma/client/deepgram` | `connectDeepgramAgent` (BYO Deepgram Voice Agent → avatar) |
88
+ | `@liforma/client/google` | `connectGeminiLive` (BYO Gemini Live → avatar) |
89
+ | `@liforma/client/livekit` | `connectLiveKitAgent` (BYO LiveKit remote track → avatar) |
84
90
  | CDN `sdk/v2/client.js` | Web components + `window.Liforma` |
85
91
 
86
92
  ## Session minting
@@ -109,6 +115,13 @@ experience.on('started', async () => {
109
115
  audio: { url: 'https://cdn.example.com/line.mp3' },
110
116
  queue: 'append'
111
117
  });
118
+
119
+ // Vendor BYO helpers (install optional peers as needed):
120
+ // import { connectElevenLabsAgent } from '@liforma/client/elevenlabs';
121
+ // import { connectOpenAiRealtime } from '@liforma/client/openai';
122
+ // import { connectDeepgramAgent } from '@liforma/client/deepgram';
123
+ // import { connectGeminiLive } from '@liforma/client/google';
124
+ // import { connectLiveKitAgent } from '@liforma/client/livekit';
112
125
  });
113
126
  ```
114
127
 
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared Web Audio capture: AudioWorklet when available, ScriptProcessor fallback.
3
+ * Used by BYO mic uplink and MediaStreamTrack → PCM piping.
4
+ */
5
+ export type AudioCaptureSession = {
6
+ stop(): void;
7
+ readonly fromSampleRate: number;
8
+ readonly usingWorklet: boolean;
9
+ };
10
+ export type StartAudioCaptureOptions = {
11
+ readonly mediaStream: MediaStream;
12
+ readonly targetSampleRate: number;
13
+ /** Called with pcm_s16le mono at targetSampleRate. */
14
+ readonly onPcm: (bytes: Uint8Array) => void;
15
+ /** When false, frames are dropped. */
16
+ readonly shouldSend?: () => boolean;
17
+ readonly onLog?: (line: string, kind?: 'info' | 'warn') => void;
18
+ };
19
+ /**
20
+ * Capture a MediaStream as pcm_s16le at targetSampleRate.
21
+ * Prefers AudioWorklet; falls back to ScriptProcessorNode.
22
+ */
23
+ export declare function startAudioCapture(options: StartAudioCaptureOptions): Promise<AudioCaptureSession>;
24
+ //# sourceMappingURL=audioCapture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioCapture.d.ts","sourceRoot":"","sources":["../../src/byo/audioCapture.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,sCAAsC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CAChE,CAAC;AAsCF;;;GAGG;AACH,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,wBAAwB,GAC/B,OAAO,CAAC,mBAAmB,CAAC,CAoI9B"}
@@ -0,0 +1,8 @@
1
+ export type { ByoBridge, ByoConnectCallbacks, ByoMicCaptureOptions, ByoPcmExperience, ByoPcmSpeechController, ByoPlayExperience, ByoPlaySpeechController, ByoSpeechUtterance } from './types';
2
+ export { BYO_PLAYBACK_GUIDANCE, MAX_PCM_CHUNK_BYTES, base64ToPcmBytes, floatToPcm16Le, parsePcmSampleRate, pcmBytesToBase64, resampleFloat32, writePcmInChunks } from './pcm';
3
+ export { clearPcmByteCarry, createPcmByteCarry, takeEvenPcmBytes, type PcmByteCarry } from './pcmCarry';
4
+ export { createCubicResampler, type FloatResampler } from './resample';
5
+ export { startAudioCapture, type AudioCaptureSession, type StartAudioCaptureOptions } from './audioCapture';
6
+ export { shouldCaptureMic, startPcmMic, type PcmMicSession, type StartPcmMicOptions } from './mic';
7
+ export { createTurnSession, type ByoTurn, type ByoTurnSession, type CreateTurnSessionOptions } from './turn';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/byo/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACN,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,MAAM,OAAO,CAAC;AAEf,OAAO,EACN,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,KAAK,YAAY,EACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEvE,OAAO,EACN,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAEnG,OAAO,EACN,iBAAiB,EACjB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,MAAM,QAAQ,CAAC"}
@@ -0,0 +1,307 @@
1
+ //#region src/byo/pcm.ts
2
+ var e = 64 * 1024, t = {
3
+ RECOMMENDED_CHUNK_MS_MIN: 20,
4
+ RECOMMENDED_CHUNK_MS_MAX: 200,
5
+ PLAYBACK_JITTER_BUFFER_MS: 150,
6
+ PLAYBACK_JITTER_BUFFER_MS_MIN: 80,
7
+ PLAYBACK_JITTER_BUFFER_MS_MAX: 250
8
+ };
9
+ function n(e) {
10
+ let t = atob(e), n = new Uint8Array(t.length);
11
+ for (let e = 0; e < t.length; e++) n[e] = t.charCodeAt(e);
12
+ return n;
13
+ }
14
+ function r(e) {
15
+ let t = "", n = 32768;
16
+ for (let r = 0; r < e.length; r += n) t += String.fromCharCode(...e.subarray(r, r + n));
17
+ return btoa(t);
18
+ }
19
+ function i(e) {
20
+ let t = new Uint8Array(e.length * 2), n = new DataView(t.buffer);
21
+ for (let t = 0; t < e.length; t++) {
22
+ let r = Math.max(-1, Math.min(1, e[t]));
23
+ n.setInt16(t * 2, r < 0 ? r * 32768 : r * 32767, !0);
24
+ }
25
+ return t;
26
+ }
27
+ function a(e, t, n) {
28
+ if (t === n || e.length === 0) return e;
29
+ let r = t / n, i = Math.max(1, Math.round(e.length / r)), a = new Float32Array(i);
30
+ for (let t = 0; t < i; t++) {
31
+ let n = t * r, i = Math.floor(n), o = Math.min(i + 1, e.length - 1), s = n - i;
32
+ a[t] = e[i] * (1 - s) + e[o] * s;
33
+ }
34
+ return a;
35
+ }
36
+ function o(e) {
37
+ let t = /^pcm_(\d+)$/.exec(e ?? "");
38
+ if (!t) return null;
39
+ let n = Number(t[1]);
40
+ return Number.isFinite(n) && n > 0 ? n : null;
41
+ }
42
+ async function s(t, n) {
43
+ if (n.byteLength === 0 || n.byteLength & 1 && (n = n.subarray(0, n.byteLength - 1), n.byteLength === 0)) return;
44
+ if (n.byteLength <= 65536) {
45
+ await t.write(n);
46
+ return;
47
+ }
48
+ let r = 0;
49
+ for (; r < n.byteLength;) {
50
+ let i = Math.min(r + e, n.byteLength);
51
+ if ((i - r) % 2 == 1 && --i, i <= r) break;
52
+ await t.write(n.subarray(r, i)), r = i;
53
+ }
54
+ }
55
+ //#endregion
56
+ //#region src/byo/pcmCarry.ts
57
+ function c() {
58
+ return { pending: null };
59
+ }
60
+ function l(e, t) {
61
+ let n = e.pending;
62
+ if (e.pending = null, n == null && t.byteLength === 0 || n == null && !(t.byteLength & 1)) return t;
63
+ let r = (n == null ? 0 : 1) + t.byteLength & -2;
64
+ if (r === 0) return e.pending = n ?? t[0] ?? null, new Uint8Array();
65
+ let i = new Uint8Array(r), a = 0;
66
+ n != null && (i[a++] = n);
67
+ let o = r - a;
68
+ return o > 0 && i.set(t.subarray(0, o), a), o < t.byteLength && (e.pending = t[t.byteLength - 1]), i;
69
+ }
70
+ function u(e) {
71
+ e.pending = null;
72
+ }
73
+ //#endregion
74
+ //#region src/byo/resample.ts
75
+ function d(e, t, n, r, i) {
76
+ let a = -.5 * e + 1.5 * t - 1.5 * n + .5 * r, o = e - 2.5 * t + 2 * n - .5 * r, s = -.5 * e + .5 * n, c = t;
77
+ return ((a * i + o) * i + s) * i + c;
78
+ }
79
+ function f(e, t) {
80
+ if (!(e > 0) || !(t > 0)) throw Error("Resampler rates must be positive");
81
+ if (e === t) return {
82
+ fromRate: e,
83
+ toRate: t,
84
+ push(e) {
85
+ return e;
86
+ },
87
+ flush() {
88
+ return new Float32Array();
89
+ }
90
+ };
91
+ let n = e / t, r = new Float32Array(), i = 0;
92
+ return {
93
+ fromRate: e,
94
+ toRate: t,
95
+ push(e) {
96
+ if (e.length === 0) return new Float32Array();
97
+ let t = new Float32Array(r.length + e.length);
98
+ if (t.set(r), t.set(e, r.length), r = t, r.length < 4) return new Float32Array();
99
+ let a = [];
100
+ for (; i + 2 < r.length - 1;) {
101
+ let e = Math.floor(i), t = i - e, o = Math.max(0, e - 1), s = Math.min(r.length - 1, e + 1), c = Math.min(r.length - 1, e + 2);
102
+ a.push(d(r[o], r[e], r[s], r[c], t)), i += n;
103
+ }
104
+ let o = Math.max(0, Math.floor(i) - 1);
105
+ return o > 0 && (r = r.subarray(o), i -= o), a.length ? Float32Array.from(a) : new Float32Array();
106
+ },
107
+ flush() {
108
+ return r.length === 0 ? new Float32Array() : (r = new Float32Array(), i = 0, new Float32Array());
109
+ }
110
+ };
111
+ }
112
+ //#endregion
113
+ //#region src/byo/audioCapture.ts
114
+ var p = "liforma-pcm-capture", m = `
115
+ class LiformaPcmCaptureProcessor extends AudioWorkletProcessor {
116
+ process(inputs) {
117
+ const ch = inputs[0] && inputs[0][0];
118
+ if (ch && ch.length) {
119
+ const copy = new Float32Array(ch.length);
120
+ copy.set(ch);
121
+ this.port.postMessage(copy, [copy.buffer]);
122
+ }
123
+ return true;
124
+ }
125
+ }
126
+ registerProcessor('${p}', LiformaPcmCaptureProcessor);
127
+ `, h = null;
128
+ function g() {
129
+ return h ||= URL.createObjectURL(new Blob([m], { type: "application/javascript" })), h;
130
+ }
131
+ function _() {
132
+ return window.AudioContext || window.webkitAudioContext || null;
133
+ }
134
+ async function v(e) {
135
+ let { mediaStream: t, targetSampleRate: n, onPcm: r, shouldSend: a, onLog: o } = e, s = _();
136
+ if (!s) throw Error("Web Audio is required for audio capture");
137
+ let c = new s(), l = c.createMediaStreamSource(t), u = c.createGain();
138
+ u.gain.value = 0;
139
+ let d = c.sampleRate, m = f(d, n), h = !1, v = !1, y = (e) => {
140
+ if (h || a && !a()) return;
141
+ let t = m.push(e);
142
+ t.length !== 0 && r(i(t));
143
+ }, b = () => void 0;
144
+ return await (async () => {
145
+ if (typeof c.audioWorklet?.addModule != "function") return !1;
146
+ try {
147
+ await c.audioWorklet.addModule(g());
148
+ let e = new AudioWorkletNode(c, p, {
149
+ numberOfInputs: 1,
150
+ numberOfOutputs: 1,
151
+ outputChannelCount: [1]
152
+ });
153
+ return e.port.onmessage = (e) => {
154
+ e.data instanceof Float32Array && y(e.data);
155
+ }, l.connect(e), e.connect(u), u.connect(c.destination), b = () => {
156
+ e.port.onmessage = null;
157
+ try {
158
+ e.disconnect();
159
+ } catch {}
160
+ try {
161
+ l.disconnect();
162
+ } catch {}
163
+ try {
164
+ u.disconnect();
165
+ } catch {}
166
+ }, v = !0, !0;
167
+ } catch {
168
+ return !1;
169
+ }
170
+ })() ? o?.(`Mic/track capture via AudioWorklet at ${d} Hz → ${n} Hz`) : ((() => {
171
+ let e = c.createScriptProcessor?.bind(c);
172
+ if (!e) throw Error("Neither AudioWorklet nor ScriptProcessor is available");
173
+ let t = e(4096, 1, 1);
174
+ t.onaudioprocess = (e) => {
175
+ y(e.inputBuffer.getChannelData(0));
176
+ }, l.connect(t), t.connect(u), u.connect(c.destination), b = () => {
177
+ t.onaudioprocess = null;
178
+ try {
179
+ t.disconnect();
180
+ } catch {}
181
+ try {
182
+ l.disconnect();
183
+ } catch {}
184
+ try {
185
+ u.disconnect();
186
+ } catch {}
187
+ }, v = !1;
188
+ })(), o?.(`Mic/track capture via ScriptProcessor at ${d} Hz → ${n} Hz`, "warn")), c.state === "suspended" && await c.resume().catch(() => void 0), {
189
+ fromSampleRate: d,
190
+ usingWorklet: v,
191
+ stop() {
192
+ h || (h = !0, m.flush(), b(), c.close().catch(() => void 0));
193
+ }
194
+ };
195
+ }
196
+ //#endregion
197
+ //#region src/byo/mic.ts
198
+ async function y(e) {
199
+ let t = e.mediaStream ?? await navigator.mediaDevices.getUserMedia({ audio: {
200
+ channelCount: 1,
201
+ echoCancellation: !0,
202
+ noiseSuppression: !0
203
+ } }), n = !e.mediaStream, r = await v({
204
+ mediaStream: t,
205
+ targetSampleRate: e.targetSampleRate,
206
+ onPcm: e.onPcm,
207
+ shouldSend: e.shouldSend,
208
+ onLog: e.onLog
209
+ });
210
+ return {
211
+ fromSampleRate: r.fromSampleRate,
212
+ usingWorklet: r.usingWorklet,
213
+ stop() {
214
+ if (r.stop(), n) for (let e of t.getTracks()) e.stop();
215
+ }
216
+ };
217
+ }
218
+ function b(e) {
219
+ return e.mediaStream ? !0 : e.captureMic !== !1;
220
+ }
221
+ //#endregion
222
+ //#region src/byo/turn.ts
223
+ function x(e, t) {
224
+ let { sampleRate: r, isEnded: i, canWrite: a, onWriteError: o } = t, d = null, f = "", p = c();
225
+ function m() {
226
+ return typeof r == "function" ? r() : r;
227
+ }
228
+ function h() {
229
+ let e = f.trim();
230
+ return f = "", e;
231
+ }
232
+ function g() {
233
+ if (d) return d;
234
+ let t = m();
235
+ if (t == null) return null;
236
+ let n = h();
237
+ return d = {
238
+ utterance: e.speech.createUtterance({
239
+ format: {
240
+ encoding: "pcm_s16le",
241
+ sampleRate: t,
242
+ channels: 1
243
+ },
244
+ queue: "replace-active",
245
+ ...n ? { transcript: n } : {}
246
+ }),
247
+ writes: Promise.resolve(),
248
+ transcript: n
249
+ }, d;
250
+ }
251
+ function _(e) {
252
+ if (i() || a && !a()) return;
253
+ let t = l(p, e);
254
+ if (t.byteLength === 0) return;
255
+ let n = g();
256
+ n && (n.writes = n.writes.then(() => s(n.utterance, t)).catch((e) => {
257
+ console.error(e), o?.(e), u(p);
258
+ let t = d;
259
+ d = null, t && t.utterance.cancel().catch(() => void 0);
260
+ }));
261
+ }
262
+ return {
263
+ getTurn: () => d,
264
+ writePcm: _,
265
+ writePcmBase64(e) {
266
+ e && _(n(e));
267
+ },
268
+ setTranscript(e, t = "append") {
269
+ let n = t === "replace" ? e.trim() : `${d?.transcript ?? f}${e}`.trim();
270
+ if (n) {
271
+ if (!d) {
272
+ f = n;
273
+ return;
274
+ }
275
+ d.transcript = n, d.utterance.setTranscript(n).catch(() => void 0);
276
+ }
277
+ },
278
+ async closeActiveTurn() {
279
+ let e = d;
280
+ if (d = null, u(p), e) try {
281
+ await e.writes;
282
+ let t = e.transcript.trim();
283
+ await e.utterance.close({
284
+ ...t ? { transcript: t } : {},
285
+ history: "none"
286
+ });
287
+ } catch {}
288
+ },
289
+ bargeIn() {
290
+ f = "", u(p);
291
+ let t = d;
292
+ d = null, t ? t.utterance.cancel() : e.speech.interrupt({ scope: "active" });
293
+ },
294
+ abandonActiveTurn() {
295
+ f = "", u(p);
296
+ let e = d;
297
+ d = null, e && e.utterance.cancel().catch(() => void 0);
298
+ },
299
+ clearPending() {
300
+ f = "";
301
+ }
302
+ };
303
+ }
304
+ //#endregion
305
+ export { t as BYO_PLAYBACK_GUIDANCE, e as MAX_PCM_CHUNK_BYTES, n as base64ToPcmBytes, u as clearPcmByteCarry, f as createCubicResampler, c as createPcmByteCarry, x as createTurnSession, i as floatToPcm16Le, o as parsePcmSampleRate, r as pcmBytesToBase64, a as resampleFloat32, b as shouldCaptureMic, v as startAudioCapture, y as startPcmMic, l as takeEvenPcmBytes, s as writePcmInChunks };
306
+
307
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/byo/pcm.ts","../../src/byo/pcmCarry.ts","../../src/byo/resample.ts","../../src/byo/audioCapture.ts","../../src/byo/mic.ts","../../src/byo/turn.ts"],"sourcesContent":["/** Liforma `speech.write` rejects chunks larger than 64 KiB. */\nexport const MAX_PCM_CHUNK_BYTES = 64 * 1024;\n\n/**\n * Playback / chunk guidance for custom BYO bridges (mirrors speech controller limits).\n * Prefer ~20–200 ms PCM frames; the player starts after an adaptive ~80–250 ms jitter\n * buffer (default 150 ms) based on chunk arrival variance.\n */\nexport const BYO_PLAYBACK_GUIDANCE = {\n\tRECOMMENDED_CHUNK_MS_MIN: 20,\n\tRECOMMENDED_CHUNK_MS_MAX: 200,\n\tPLAYBACK_JITTER_BUFFER_MS: 150,\n\tPLAYBACK_JITTER_BUFFER_MS_MIN: 80,\n\tPLAYBACK_JITTER_BUFFER_MS_MAX: 250\n} as const;\n\nexport function base64ToPcmBytes(b64: string): Uint8Array {\n\tconst bin = atob(b64);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn out;\n}\n\nexport function pcmBytesToBase64(bytes: Uint8Array): string {\n\tlet bin = '';\n\tconst chunk = 0x8000;\n\tfor (let i = 0; i < bytes.length; i += chunk) {\n\t\tbin += String.fromCharCode(...bytes.subarray(i, i + chunk));\n\t}\n\treturn btoa(bin);\n}\n\nexport function floatToPcm16Le(input: Float32Array): Uint8Array {\n\tconst out = new Uint8Array(input.length * 2);\n\tconst view = new DataView(out.buffer);\n\tfor (let i = 0; i < input.length; i++) {\n\t\tconst s = Math.max(-1, Math.min(1, input[i]!));\n\t\tview.setInt16(i * 2, s < 0 ? s * 0x8000 : s * 0x7fff, true);\n\t}\n\treturn out;\n}\n\n/** Naive linear resample Float32 → target rate. */\nexport function resampleFloat32(input: Float32Array, fromRate: number, toRate: number): Float32Array {\n\tif (fromRate === toRate || input.length === 0) return input;\n\tconst ratio = fromRate / toRate;\n\tconst outLen = Math.max(1, Math.round(input.length / ratio));\n\tconst out = new Float32Array(outLen);\n\tfor (let i = 0; i < outLen; i++) {\n\t\tconst src = i * ratio;\n\t\tconst i0 = Math.floor(src);\n\t\tconst i1 = Math.min(i0 + 1, input.length - 1);\n\t\tconst t = src - i0;\n\t\tout[i] = input[i0]! * (1 - t) + input[i1]! * t;\n\t}\n\treturn out;\n}\n\n/**\n * Parse vendor format strings like `pcm_16000` / `pcm_24000`.\n * Returns null when the format is missing or not PCM.\n */\nexport function parsePcmSampleRate(format: string | null | undefined): number | null {\n\tconst m = /^pcm_(\\d+)$/.exec(format ?? '');\n\tif (!m) return null;\n\tconst rate = Number(m[1]);\n\treturn Number.isFinite(rate) && rate > 0 ? rate : null;\n}\n\n/**\n * Write even-length PCM, splitting at MAX_PCM_CHUNK_BYTES on even boundaries.\n * Callers that may receive odd-length frames should run bytes through\n * {@link takeEvenPcmBytes} first so samples are not dropped across chunks.\n */\nexport async function writePcmInChunks(\n\tutterance: { write: (chunk: ArrayBuffer | ArrayBufferView) => Promise<unknown> },\n\tbytes: Uint8Array\n): Promise<void> {\n\tif (bytes.byteLength === 0) return;\n\tif ((bytes.byteLength & 1) !== 0) {\n\t\t// Defensive: drop trailing unpaired byte within a single call.\n\t\tbytes = bytes.subarray(0, bytes.byteLength - 1);\n\t\tif (bytes.byteLength === 0) return;\n\t}\n\tif (bytes.byteLength <= MAX_PCM_CHUNK_BYTES) {\n\t\tawait utterance.write(bytes);\n\t\treturn;\n\t}\n\tlet offset = 0;\n\twhile (offset < bytes.byteLength) {\n\t\tlet end = Math.min(offset + MAX_PCM_CHUNK_BYTES, bytes.byteLength);\n\t\tif ((end - offset) % 2 === 1) end -= 1;\n\t\tif (end <= offset) break;\n\t\tawait utterance.write(bytes.subarray(offset, end));\n\t\toffset = end;\n\t}\n}\n","/**\n * Carry an odd trailing PCM byte across chunk boundaries so we never silently\n * drop a sample when vendor frames are odd-length or split mid-sample.\n */\n\nexport type PcmByteCarry = {\n\t/** Pending unpaired byte waiting for its mate; null when 16-bit aligned. */\n\tpending: number | null;\n};\n\nexport function createPcmByteCarry(): PcmByteCarry {\n\treturn { pending: null };\n}\n\n/**\n * Merge carry + incoming bytes into an even-length buffer for write, updating carry.\n * Returns a (possibly empty) even-length Uint8Array ready for utterance.write.\n */\nexport function takeEvenPcmBytes(carry: PcmByteCarry, incoming: Uint8Array): Uint8Array {\n\tconst prefix = carry.pending;\n\tcarry.pending = null;\n\n\tif (prefix == null && incoming.byteLength === 0) {\n\t\treturn incoming;\n\t}\n\n\t// Fast path: aligned and already even — no copy.\n\tif (prefix == null && (incoming.byteLength & 1) === 0) {\n\t\treturn incoming;\n\t}\n\n\tconst totalLen = (prefix != null ? 1 : 0) + incoming.byteLength;\n\tconst evenLen = totalLen & ~1;\n\n\tif (evenLen === 0) {\n\t\t// Single unpaired byte overall — keep it for the next call.\n\t\tcarry.pending = prefix ?? incoming[0] ?? null;\n\t\treturn new Uint8Array(0);\n\t}\n\n\tconst out = new Uint8Array(evenLen);\n\tlet o = 0;\n\tif (prefix != null) {\n\t\tout[o++] = prefix;\n\t}\n\tconst fromIncoming = evenLen - o;\n\tif (fromIncoming > 0) {\n\t\tout.set(incoming.subarray(0, fromIncoming), o);\n\t}\n\tif (fromIncoming < incoming.byteLength) {\n\t\tcarry.pending = incoming[incoming.byteLength - 1]!;\n\t}\n\treturn out;\n}\n\n/** Drop any leftover odd byte (end of utterance). */\nexport function clearPcmByteCarry(carry: PcmByteCarry): void {\n\tcarry.pending = null;\n}\n","/**\n * Stateful cubic (Catmull-Rom) resampler for continuous PCM capture streams.\n * Better than naive linear for uplink mic / track capture.\n */\n\nexport type FloatResampler = {\n\t/** Push a mono float buffer; returns samples at the target rate (may be empty). */\n\tpush(input: Float32Array): Float32Array;\n\t/** Flush any remaining fractional sample (usually silence). */\n\tflush(): Float32Array;\n\treadonly fromRate: number;\n\treadonly toRate: number;\n};\n\nfunction catmullRom(y0: number, y1: number, y2: number, y3: number, t: number): number {\n\tconst a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3;\n\tconst a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3;\n\tconst a2 = -0.5 * y0 + 0.5 * y2;\n\tconst a3 = y1;\n\treturn ((a0 * t + a1) * t + a2) * t + a3;\n}\n\n/**\n * Create a continuous resampler. When rates match, push() returns the input as-is\n * (no copy) when possible.\n */\nexport function createCubicResampler(fromRate: number, toRate: number): FloatResampler {\n\tif (!(fromRate > 0) || !(toRate > 0)) {\n\t\tthrow new Error('Resampler rates must be positive');\n\t}\n\n\tif (fromRate === toRate) {\n\t\treturn {\n\t\t\tfromRate,\n\t\t\ttoRate,\n\t\t\tpush(input) {\n\t\t\t\treturn input;\n\t\t\t},\n\t\t\tflush() {\n\t\t\t\treturn new Float32Array(0);\n\t\t\t}\n\t\t};\n\t}\n\n\tconst ratio = fromRate / toRate;\n\t/** History + pending input (keeps 3 samples of lookbehind for cubic). */\n\tlet pending = new Float32Array(0);\n\tlet pos = 0; // position in pending sample space for next output\n\n\treturn {\n\t\tfromRate,\n\t\ttoRate,\n\t\tpush(input) {\n\t\t\tif (input.length === 0) return new Float32Array(0);\n\t\t\tconst merged = new Float32Array(pending.length + input.length);\n\t\t\tmerged.set(pending);\n\t\t\tmerged.set(input, pending.length);\n\t\t\tpending = merged;\n\n\t\t\tif (pending.length < 4) return new Float32Array(0);\n\n\t\t\tconst out: number[] = [];\n\t\t\t// Need i+2 available for cubic; stop while pos+2 < length-1 equivalently floor(pos)+2 < len-?\n\t\t\twhile (pos + 2 < pending.length - 1) {\n\t\t\t\tconst i1 = Math.floor(pos);\n\t\t\t\tconst t = pos - i1;\n\t\t\t\tconst i0 = Math.max(0, i1 - 1);\n\t\t\t\tconst i2 = Math.min(pending.length - 1, i1 + 1);\n\t\t\t\tconst i3 = Math.min(pending.length - 1, i1 + 2);\n\t\t\t\tout.push(\n\t\t\t\t\tcatmullRom(pending[i0]!, pending[i1]!, pending[i2]!, pending[i3]!, t)\n\t\t\t\t);\n\t\t\t\tpos += ratio;\n\t\t\t}\n\n\t\t\t// Drop consumed samples but keep lookbehind (≥1) and unread tail.\n\t\t\tconst drop = Math.max(0, Math.floor(pos) - 1);\n\t\t\tif (drop > 0) {\n\t\t\t\tpending = pending.subarray(drop);\n\t\t\t\tpos -= drop;\n\t\t\t}\n\n\t\t\treturn out.length ? Float32Array.from(out) : new Float32Array(0);\n\t\t},\n\t\tflush() {\n\t\t\tif (pending.length === 0) return new Float32Array(0);\n\t\t\t// Emit one last sample if we have enough history; otherwise drop.\n\t\t\tpending = new Float32Array(0);\n\t\t\tpos = 0;\n\t\t\treturn new Float32Array(0);\n\t\t}\n\t};\n}\n","/**\n * Shared Web Audio capture: AudioWorklet when available, ScriptProcessor fallback.\n * Used by BYO mic uplink and MediaStreamTrack → PCM piping.\n */\n\nimport { floatToPcm16Le } from './pcm';\nimport { createCubicResampler, type FloatResampler } from './resample';\n\nexport type AudioCaptureSession = {\n\tstop(): void;\n\treadonly fromSampleRate: number;\n\treadonly usingWorklet: boolean;\n};\n\nexport type StartAudioCaptureOptions = {\n\treadonly mediaStream: MediaStream;\n\treadonly targetSampleRate: number;\n\t/** Called with pcm_s16le mono at targetSampleRate. */\n\treadonly onPcm: (bytes: Uint8Array) => void;\n\t/** When false, frames are dropped. */\n\treadonly shouldSend?: () => boolean;\n\treadonly onLog?: (line: string, kind?: 'info' | 'warn') => void;\n};\n\nconst WORKLET_NAME = 'liforma-pcm-capture';\n\nconst WORKLET_SOURCE = `\nclass LiformaPcmCaptureProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const ch = inputs[0] && inputs[0][0];\n if (ch && ch.length) {\n const copy = new Float32Array(ch.length);\n copy.set(ch);\n this.port.postMessage(copy, [copy.buffer]);\n }\n return true;\n }\n}\nregisterProcessor('${WORKLET_NAME}', LiformaPcmCaptureProcessor);\n`;\n\nlet workletUrl: string | null = null;\n\nfunction getWorkletUrl(): string {\n\tif (!workletUrl) {\n\t\tworkletUrl = URL.createObjectURL(\n\t\t\tnew Blob([WORKLET_SOURCE], { type: 'application/javascript' })\n\t\t);\n\t}\n\treturn workletUrl;\n}\n\nfunction getAudioContextCtor(): typeof AudioContext | null {\n\treturn (\n\t\twindow.AudioContext ||\n\t\t(window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext ||\n\t\tnull\n\t);\n}\n\n/**\n * Capture a MediaStream as pcm_s16le at targetSampleRate.\n * Prefers AudioWorklet; falls back to ScriptProcessorNode.\n */\nexport async function startAudioCapture(\n\toptions: StartAudioCaptureOptions\n): Promise<AudioCaptureSession> {\n\tconst { mediaStream, targetSampleRate, onPcm, shouldSend, onLog } = options;\n\tconst AudioContextCtor = getAudioContextCtor();\n\tif (!AudioContextCtor) throw new Error('Web Audio is required for audio capture');\n\n\tconst audioCtx = new AudioContextCtor();\n\tconst source = audioCtx.createMediaStreamSource(mediaStream);\n\tconst silent = audioCtx.createGain();\n\tsilent.gain.value = 0;\n\tconst fromSampleRate = audioCtx.sampleRate;\n\tconst resampler: FloatResampler = createCubicResampler(fromSampleRate, targetSampleRate);\n\tlet stopped = false;\n\tlet usingWorklet = false;\n\n\tconst emitFloat = (input: Float32Array) => {\n\t\tif (stopped) return;\n\t\tif (shouldSend && !shouldSend()) return;\n\t\tconst resampled = resampler.push(input);\n\t\tif (resampled.length === 0) return;\n\t\tonPcm(floatToPcm16Le(resampled));\n\t};\n\n\tlet disconnectGraph: () => void = () => undefined;\n\n\tconst tryWorklet = async (): Promise<boolean> => {\n\t\tif (typeof audioCtx.audioWorklet?.addModule !== 'function') return false;\n\t\ttry {\n\t\t\tawait audioCtx.audioWorklet.addModule(getWorkletUrl());\n\t\t\tconst node = new AudioWorkletNode(audioCtx, WORKLET_NAME, {\n\t\t\t\tnumberOfInputs: 1,\n\t\t\t\tnumberOfOutputs: 1,\n\t\t\t\toutputChannelCount: [1]\n\t\t\t});\n\t\t\tnode.port.onmessage = (ev) => {\n\t\t\t\tif (ev.data instanceof Float32Array) emitFloat(ev.data);\n\t\t\t};\n\t\t\tsource.connect(node);\n\t\t\tnode.connect(silent);\n\t\t\tsilent.connect(audioCtx.destination);\n\t\t\tdisconnectGraph = () => {\n\t\t\t\tnode.port.onmessage = null;\n\t\t\t\ttry {\n\t\t\t\t\tnode.disconnect();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tsource.disconnect();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tsilent.disconnect();\n\t\t\t\t} catch {\n\t\t\t\t\t/* ignore */\n\t\t\t\t}\n\t\t\t};\n\t\t\tusingWorklet = true;\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n\n\tconst useScriptProcessor = (): void => {\n\t\tconst createScriptProcessor = (\n\t\t\taudioCtx as AudioContext & {\n\t\t\t\tcreateScriptProcessor?: (\n\t\t\t\t\tbufferSize: number,\n\t\t\t\t\tinputChannels: number,\n\t\t\t\t\toutputChannels: number\n\t\t\t\t) => ScriptProcessorNode;\n\t\t\t}\n\t\t).createScriptProcessor?.bind(audioCtx);\n\n\t\tif (!createScriptProcessor) {\n\t\t\tthrow new Error('Neither AudioWorklet nor ScriptProcessor is available');\n\t\t}\n\n\t\tconst processor = createScriptProcessor(4096, 1, 1);\n\t\tprocessor.onaudioprocess = (ev) => {\n\t\t\temitFloat(ev.inputBuffer.getChannelData(0));\n\t\t};\n\t\tsource.connect(processor);\n\t\tprocessor.connect(silent);\n\t\tsilent.connect(audioCtx.destination);\n\t\tdisconnectGraph = () => {\n\t\t\tprocessor.onaudioprocess = null;\n\t\t\ttry {\n\t\t\t\tprocessor.disconnect();\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tsource.disconnect();\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tsilent.disconnect();\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t};\n\t\tusingWorklet = false;\n\t};\n\n\tif (!(await tryWorklet())) {\n\t\tuseScriptProcessor();\n\t\tonLog?.(\n\t\t\t`Mic/track capture via ScriptProcessor at ${fromSampleRate} Hz → ${targetSampleRate} Hz`,\n\t\t\t'warn'\n\t\t);\n\t} else {\n\t\tonLog?.(`Mic/track capture via AudioWorklet at ${fromSampleRate} Hz → ${targetSampleRate} Hz`);\n\t}\n\n\tif (audioCtx.state === 'suspended') {\n\t\tawait audioCtx.resume().catch(() => undefined);\n\t}\n\n\treturn {\n\t\tfromSampleRate,\n\t\tusingWorklet,\n\t\tstop() {\n\t\t\tif (stopped) return;\n\t\t\tstopped = true;\n\t\t\tresampler.flush();\n\t\t\tdisconnectGraph();\n\t\t\tvoid audioCtx.close().catch(() => undefined);\n\t\t}\n\t};\n}\n","import { startAudioCapture } from './audioCapture';\n\nexport type StartPcmMicOptions = {\n\treadonly targetSampleRate: number;\n\t/** Existing stream; when omitted, requests getUserMedia. */\n\treadonly mediaStream?: MediaStream;\n\t/** Called with pcm_s16le mono chunks at targetSampleRate. */\n\treadonly onPcm: (bytes: Uint8Array) => void;\n\t/** When false, PCM frames are dropped (e.g. before SettingsApplied). */\n\treadonly shouldSend?: () => boolean;\n\treadonly onLog?: (line: string, kind?: 'info' | 'warn') => void;\n};\n\nexport type PcmMicSession = {\n\tstop(): void;\n\treadonly fromSampleRate: number;\n\t/** True when capture used AudioWorklet (false = ScriptProcessor fallback). */\n\treadonly usingWorklet: boolean;\n};\n\n/**\n * Capture mic (or an existing MediaStream), resample to targetRate, emit pcm_s16le.\n * Prefers AudioWorklet + cubic resample; falls back to ScriptProcessor.\n */\nexport async function startPcmMic(options: StartPcmMicOptions): Promise<PcmMicSession> {\n\tconst ownedStream =\n\t\toptions.mediaStream ??\n\t\t(await navigator.mediaDevices.getUserMedia({\n\t\t\taudio: {\n\t\t\t\tchannelCount: 1,\n\t\t\t\techoCancellation: true,\n\t\t\t\tnoiseSuppression: true\n\t\t\t}\n\t\t}));\n\tconst stopTracksOnStop = !options.mediaStream;\n\n\tconst capture = await startAudioCapture({\n\t\tmediaStream: ownedStream,\n\t\ttargetSampleRate: options.targetSampleRate,\n\t\tonPcm: options.onPcm,\n\t\tshouldSend: options.shouldSend,\n\t\tonLog: options.onLog\n\t});\n\n\treturn {\n\t\tfromSampleRate: capture.fromSampleRate,\n\t\tusingWorklet: capture.usingWorklet,\n\t\tstop() {\n\t\t\tcapture.stop();\n\t\t\tif (stopTracksOnStop) {\n\t\t\t\tfor (const track of ownedStream.getTracks()) track.stop();\n\t\t\t}\n\t\t}\n\t};\n}\n\n/** Resolve whether to start mic from shared BYO mic options. */\nexport function shouldCaptureMic(options: {\n\tcaptureMic?: boolean;\n\tmediaStream?: MediaStream;\n}): boolean {\n\tif (options.mediaStream) return true;\n\treturn options.captureMic !== false;\n}\n","import { base64ToPcmBytes, writePcmInChunks } from './pcm';\nimport { clearPcmByteCarry, createPcmByteCarry, takeEvenPcmBytes } from './pcmCarry';\nimport type { ByoPcmExperience, ByoSpeechUtterance } from './types';\n\nexport type ByoTurn = {\n\tutterance: ByoSpeechUtterance;\n\twrites: Promise<void>;\n\ttranscript: string;\n};\n\nexport type CreateTurnSessionOptions = {\n\t/**\n\t * Fixed sample rate, or a getter when the vendor locks rate after connect\n\t * (e.g. ElevenLabs `agent_output_audio_format`).\n\t */\n\treadonly sampleRate: number | (() => number | null);\n\treadonly isEnded: () => boolean;\n\t/** When false, writePcm is a no-op (e.g. before Deepgram SettingsApplied). */\n\treadonly canWrite?: () => boolean;\n\treadonly onWriteError?: (error: unknown) => void;\n};\n\nexport type ByoTurnSession = {\n\tgetTurn(): ByoTurn | null;\n\twritePcm(bytes: Uint8Array): void;\n\twritePcmBase64(b64: string): void;\n\tsetTranscript(text: string, mode?: 'replace' | 'append'): void;\n\t/** Await pending writes and close the utterance (normal turn end). */\n\tcloseActiveTurn(): Promise<void>;\n\t/** Cancel active utterance / interrupt (user barge-in). */\n\tbargeIn(): void;\n\t/**\n\t * Drop the active turn on unexpected disconnect — cancel if open so the\n\t * player does not keep an orphaned utterance.\n\t * Does **not** call `speech.interrupt` when idle (safe after a clean `end()`).\n\t */\n\tabandonActiveTurn(): void;\n\tclearPending(): void;\n};\n\n/**\n * Shared per-turn PCM + transcript state machine for BYO createUtterance bridges.\n */\nexport function createTurnSession(\n\texperience: ByoPcmExperience,\n\toptions: CreateTurnSessionOptions\n): ByoTurnSession {\n\tconst { sampleRate: sampleRateOpt, isEnded, canWrite, onWriteError } = options;\n\n\tlet turn: ByoTurn | null = null;\n\tlet pendingTranscript = '';\n\tconst pcmCarry = createPcmByteCarry();\n\n\tfunction resolveSampleRate(): number | null {\n\t\treturn typeof sampleRateOpt === 'function' ? sampleRateOpt() : sampleRateOpt;\n\t}\n\n\tfunction consumePendingTranscript(): string {\n\t\tconst text = pendingTranscript.trim();\n\t\tpendingTranscript = '';\n\t\treturn text;\n\t}\n\n\tfunction openTurnIfNeeded(): ByoTurn | null {\n\t\tif (turn) return turn;\n\t\tconst sampleRate = resolveSampleRate();\n\t\tif (sampleRate == null) return null;\n\t\tconst seed = consumePendingTranscript();\n\t\tconst utterance = experience.speech.createUtterance({\n\t\t\tformat: { encoding: 'pcm_s16le', sampleRate, channels: 1 },\n\t\t\tqueue: 'replace-active',\n\t\t\t...(seed ? { transcript: seed } : {})\n\t\t});\n\t\tturn = { utterance, writes: Promise.resolve(), transcript: seed };\n\t\treturn turn;\n\t}\n\n\tfunction writePcm(bytes: Uint8Array): void {\n\t\tif (isEnded()) return;\n\t\tif (canWrite && !canWrite()) return;\n\t\tconst even = takeEvenPcmBytes(pcmCarry, bytes);\n\t\tif (even.byteLength === 0) return;\n\t\tconst current = openTurnIfNeeded();\n\t\tif (!current) return;\n\t\tcurrent.writes = current.writes\n\t\t\t.then(() => writePcmInChunks(current.utterance, even))\n\t\t\t.catch((err) => {\n\t\t\t\tconsole.error(err);\n\t\t\t\tonWriteError?.(err);\n\t\t\t\tclearPcmByteCarry(pcmCarry);\n\t\t\t\t// Drop the half-open utterance so hosts are not stuck until vendor close.\n\t\t\t\tconst failed = turn;\n\t\t\t\tturn = null;\n\t\t\t\tif (failed) {\n\t\t\t\t\tvoid failed.utterance.cancel().catch(() => undefined);\n\t\t\t\t}\n\t\t\t});\n\t}\n\n\treturn {\n\t\tgetTurn: () => turn,\n\n\t\twritePcm,\n\n\t\twritePcmBase64(b64: string) {\n\t\t\tif (!b64) return;\n\t\t\twritePcm(base64ToPcmBytes(b64));\n\t\t},\n\n\t\tsetTranscript(text: string, mode: 'replace' | 'append' = 'append') {\n\t\t\tconst next =\n\t\t\t\tmode === 'replace'\n\t\t\t\t\t? text.trim()\n\t\t\t\t\t: `${turn?.transcript ?? pendingTranscript}${text}`.trim();\n\t\t\tif (!next) return;\n\t\t\tif (!turn) {\n\t\t\t\tpendingTranscript = next;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tturn.transcript = next;\n\t\t\tvoid turn.utterance.setTranscript(next).catch(() => undefined);\n\t\t},\n\n\t\tasync closeActiveTurn() {\n\t\t\tconst current = turn;\n\t\t\tturn = null;\n\t\t\tclearPcmByteCarry(pcmCarry);\n\t\t\tif (!current) return;\n\t\t\ttry {\n\t\t\t\tawait current.writes;\n\t\t\t\tconst transcript = current.transcript.trim();\n\t\t\t\tawait current.utterance.close({\n\t\t\t\t\t...(transcript ? { transcript } : {}),\n\t\t\t\t\thistory: 'none'\n\t\t\t\t});\n\t\t\t} catch {\n\t\t\t\t/* ignore */\n\t\t\t}\n\t\t},\n\n\t\tbargeIn() {\n\t\t\tpendingTranscript = '';\n\t\t\tclearPcmByteCarry(pcmCarry);\n\t\t\tconst current = turn;\n\t\t\tturn = null;\n\t\t\tvoid (current\n\t\t\t\t? current.utterance.cancel()\n\t\t\t\t: experience.speech.interrupt({ scope: 'active' }));\n\t\t},\n\n\t\tabandonActiveTurn() {\n\t\t\tpendingTranscript = '';\n\t\t\tclearPcmByteCarry(pcmCarry);\n\t\t\tconst current = turn;\n\t\t\tturn = null;\n\t\t\t// Cancel only — never interrupt when idle. Close handlers also run after a\n\t\t\t// clean end()/closeActiveTurn(); interrupt-when-idle would clobber the next speak.\n\t\t\tif (current) {\n\t\t\t\tvoid current.utterance.cancel().catch(() => undefined);\n\t\t\t}\n\t\t},\n\n\t\tclearPending() {\n\t\t\tpendingTranscript = '';\n\t\t}\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export type StartPcmMicOptions = {
2
+ readonly targetSampleRate: number;
3
+ /** Existing stream; when omitted, requests getUserMedia. */
4
+ readonly mediaStream?: MediaStream;
5
+ /** Called with pcm_s16le mono chunks at targetSampleRate. */
6
+ readonly onPcm: (bytes: Uint8Array) => void;
7
+ /** When false, PCM frames are dropped (e.g. before SettingsApplied). */
8
+ readonly shouldSend?: () => boolean;
9
+ readonly onLog?: (line: string, kind?: 'info' | 'warn') => void;
10
+ };
11
+ export type PcmMicSession = {
12
+ stop(): void;
13
+ readonly fromSampleRate: number;
14
+ /** True when capture used AudioWorklet (false = ScriptProcessor fallback). */
15
+ readonly usingWorklet: boolean;
16
+ };
17
+ /**
18
+ * Capture mic (or an existing MediaStream), resample to targetRate, emit pcm_s16le.
19
+ * Prefers AudioWorklet + cubic resample; falls back to ScriptProcessor.
20
+ */
21
+ export declare function startPcmMic(options: StartPcmMicOptions): Promise<PcmMicSession>;
22
+ /** Resolve whether to start mic from shared BYO mic options. */
23
+ export declare function shouldCaptureMic(options: {
24
+ captureMic?: boolean;
25
+ mediaStream?: MediaStream;
26
+ }): boolean;
27
+ //# sourceMappingURL=mic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mic.d.ts","sourceRoot":"","sources":["../../src/byo/mic.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;CAChE,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8BrF;AAED,gEAAgE;AAChE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACzC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B,GAAG,OAAO,CAGV"}
@@ -0,0 +1,33 @@
1
+ /** Liforma `speech.write` rejects chunks larger than 64 KiB. */
2
+ export declare const MAX_PCM_CHUNK_BYTES: number;
3
+ /**
4
+ * Playback / chunk guidance for custom BYO bridges (mirrors speech controller limits).
5
+ * Prefer ~20–200 ms PCM frames; the player starts after an adaptive ~80–250 ms jitter
6
+ * buffer (default 150 ms) based on chunk arrival variance.
7
+ */
8
+ export declare const BYO_PLAYBACK_GUIDANCE: {
9
+ readonly RECOMMENDED_CHUNK_MS_MIN: 20;
10
+ readonly RECOMMENDED_CHUNK_MS_MAX: 200;
11
+ readonly PLAYBACK_JITTER_BUFFER_MS: 150;
12
+ readonly PLAYBACK_JITTER_BUFFER_MS_MIN: 80;
13
+ readonly PLAYBACK_JITTER_BUFFER_MS_MAX: 250;
14
+ };
15
+ export declare function base64ToPcmBytes(b64: string): Uint8Array;
16
+ export declare function pcmBytesToBase64(bytes: Uint8Array): string;
17
+ export declare function floatToPcm16Le(input: Float32Array): Uint8Array;
18
+ /** Naive linear resample Float32 → target rate. */
19
+ export declare function resampleFloat32(input: Float32Array, fromRate: number, toRate: number): Float32Array;
20
+ /**
21
+ * Parse vendor format strings like `pcm_16000` / `pcm_24000`.
22
+ * Returns null when the format is missing or not PCM.
23
+ */
24
+ export declare function parsePcmSampleRate(format: string | null | undefined): number | null;
25
+ /**
26
+ * Write even-length PCM, splitting at MAX_PCM_CHUNK_BYTES on even boundaries.
27
+ * Callers that may receive odd-length frames should run bytes through
28
+ * {@link takeEvenPcmBytes} first so samples are not dropped across chunks.
29
+ */
30
+ export declare function writePcmInChunks(utterance: {
31
+ write: (chunk: ArrayBuffer | ArrayBufferView) => Promise<unknown>;
32
+ }, bytes: Uint8Array): Promise<void>;
33
+ //# sourceMappingURL=pcm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcm.d.ts","sourceRoot":"","sources":["../../src/byo/pcm.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,qBAAqB;;;;;;CAMxB,CAAC;AAEX,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAKxD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAO1D;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,CAQ9D;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,CAanG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAKnF;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACrC,SAAS,EAAE;IAAE,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,eAAe,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAChF,KAAK,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAmBf"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Carry an odd trailing PCM byte across chunk boundaries so we never silently
3
+ * drop a sample when vendor frames are odd-length or split mid-sample.
4
+ */
5
+ export type PcmByteCarry = {
6
+ /** Pending unpaired byte waiting for its mate; null when 16-bit aligned. */
7
+ pending: number | null;
8
+ };
9
+ export declare function createPcmByteCarry(): PcmByteCarry;
10
+ /**
11
+ * Merge carry + incoming bytes into an even-length buffer for write, updating carry.
12
+ * Returns a (possibly empty) even-length Uint8Array ready for utterance.write.
13
+ */
14
+ export declare function takeEvenPcmBytes(carry: PcmByteCarry, incoming: Uint8Array): Uint8Array;
15
+ /** Drop any leftover odd byte (end of utterance). */
16
+ export declare function clearPcmByteCarry(carry: PcmByteCarry): void;
17
+ //# sourceMappingURL=pcmCarry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pcmCarry.d.ts","sourceRoot":"","sources":["../../src/byo/pcmCarry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG;IAC1B,4EAA4E;IAC5E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,YAAY,CAEjD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,CAmCtF;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAE3D"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stateful cubic (Catmull-Rom) resampler for continuous PCM capture streams.
3
+ * Better than naive linear for uplink mic / track capture.
4
+ */
5
+ export type FloatResampler = {
6
+ /** Push a mono float buffer; returns samples at the target rate (may be empty). */
7
+ push(input: Float32Array): Float32Array;
8
+ /** Flush any remaining fractional sample (usually silence). */
9
+ flush(): Float32Array;
10
+ readonly fromRate: number;
11
+ readonly toRate: number;
12
+ };
13
+ /**
14
+ * Create a continuous resampler. When rates match, push() returns the input as-is
15
+ * (no copy) when possible.
16
+ */
17
+ export declare function createCubicResampler(fromRate: number, toRate: number): FloatResampler;
18
+ //# sourceMappingURL=resample.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resample.d.ts","sourceRoot":"","sources":["../../src/byo/resample.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC5B,mFAAmF;IACnF,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAAC;IACxC,+DAA+D;IAC/D,KAAK,IAAI,YAAY,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAUF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAkErF"}
@@ -0,0 +1,39 @@
1
+ import type { ByoPcmExperience, ByoSpeechUtterance } from './types';
2
+ export type ByoTurn = {
3
+ utterance: ByoSpeechUtterance;
4
+ writes: Promise<void>;
5
+ transcript: string;
6
+ };
7
+ export type CreateTurnSessionOptions = {
8
+ /**
9
+ * Fixed sample rate, or a getter when the vendor locks rate after connect
10
+ * (e.g. ElevenLabs `agent_output_audio_format`).
11
+ */
12
+ readonly sampleRate: number | (() => number | null);
13
+ readonly isEnded: () => boolean;
14
+ /** When false, writePcm is a no-op (e.g. before Deepgram SettingsApplied). */
15
+ readonly canWrite?: () => boolean;
16
+ readonly onWriteError?: (error: unknown) => void;
17
+ };
18
+ export type ByoTurnSession = {
19
+ getTurn(): ByoTurn | null;
20
+ writePcm(bytes: Uint8Array): void;
21
+ writePcmBase64(b64: string): void;
22
+ setTranscript(text: string, mode?: 'replace' | 'append'): void;
23
+ /** Await pending writes and close the utterance (normal turn end). */
24
+ closeActiveTurn(): Promise<void>;
25
+ /** Cancel active utterance / interrupt (user barge-in). */
26
+ bargeIn(): void;
27
+ /**
28
+ * Drop the active turn on unexpected disconnect — cancel if open so the
29
+ * player does not keep an orphaned utterance.
30
+ * Does **not** call `speech.interrupt` when idle (safe after a clean `end()`).
31
+ */
32
+ abandonActiveTurn(): void;
33
+ clearPending(): void;
34
+ };
35
+ /**
36
+ * Shared per-turn PCM + transcript state machine for BYO createUtterance bridges.
37
+ */
38
+ export declare function createTurnSession(experience: ByoPcmExperience, options: CreateTurnSessionOptions): ByoTurnSession;
39
+ //# sourceMappingURL=turn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.d.ts","sourceRoot":"","sources":["../../src/byo/turn.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEpE,MAAM,MAAM,OAAO,GAAG;IACrB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACtC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,IAAI,OAAO,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/D,sEAAsE;IACtE,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,2DAA2D;IAC3D,OAAO,IAAI,IAAI,CAAC;IAChB;;;;OAIG;IACH,iBAAiB,IAAI,IAAI,CAAC;IAC1B,YAAY,IAAI,IAAI,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,wBAAgB,iBAAiB,CAChC,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,wBAAwB,GAC/B,cAAc,CAwHhB"}