@ai-ecoverse/pi-audio 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/chunk-speech.js +135 -0
- package/dist/chunk-speech.js.map +7 -0
- package/dist/convert.js +294 -0
- package/dist/convert.js.map +7 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +2 -2
- package/dist/registry.js +4 -0
- package/dist/registry.js.map +2 -2
- package/dist/sentences.js +88 -0
- package/dist/sentences.js.map +7 -0
- package/dist/transcription.js.map +2 -2
- package/dist/types/chunk-speech.d.ts +4 -0
- package/dist/types/convert.d.ts +5 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/sentences.d.ts +5 -0
- package/dist/types/transcription.d.ts +2 -0
- package/dist/types/vad-listen.d.ts +7 -0
- package/dist/types/vad.d.ts +13 -0
- package/dist/types/worklet/convert.d.ts +30 -0
- package/dist/vad-listen.js +223 -0
- package/dist/vad-listen.js.map +7 -0
- package/dist/vad.js +57 -0
- package/dist/vad.js.map +7 -0
- package/dist/worklet/convert.js +80 -0
- package/dist/worklet/convert.js.map +7 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ addAudioCounters(total, usage);
|
|
|
21
21
|
|
|
22
22
|
pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatches in `patches/patches.json`.
|
|
23
23
|
|
|
24
|
+
`convertAudio` and `convertAudioSource` convert between `AudioFormat`s: PCM16, float32, G.711 μ-law and A-law, mono and stereo, sample rate, and PCM16 WAV. Opus, MP3 and FLAC pass through only when the format does not change. `encodeAudioBase64` and `decodeAudioBase64` are the wire helpers for JSON protocols. `AudioChunk.data` stays binary. The same converter runs in an AudioWorklet from `@ai-ecoverse/pi-audio/worklet` (`AUDIO_CONVERT_PROCESSOR`, `registerAudioConvertWorklet`). Adapters declare `inputFormats` and `outputFormats` and do not resample on their own.
|
|
25
|
+
|
|
24
26
|
## Development
|
|
25
27
|
|
|
26
28
|
```sh
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { blankUsage } from "./blank-usage.js";
|
|
2
|
+
import { VoiceRequestError } from "./errors.js";
|
|
3
|
+
import { sentencesOf } from "./sentences.js";
|
|
4
|
+
import {
|
|
5
|
+
SpeechEventStream
|
|
6
|
+
} from "./speech.js";
|
|
7
|
+
import { addAudioCounters } from "./usage.js";
|
|
8
|
+
function shiftSpan(span, offset) {
|
|
9
|
+
if ("entryId" in span) {
|
|
10
|
+
return {
|
|
11
|
+
entryId: span.entryId,
|
|
12
|
+
contentIndex: span.contentIndex,
|
|
13
|
+
start: span.start + offset,
|
|
14
|
+
end: span.end + offset
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return { itemId: span.itemId, start: span.start + offset, end: span.end + offset };
|
|
18
|
+
}
|
|
19
|
+
function mergeUsage(usages) {
|
|
20
|
+
const total = blankUsage();
|
|
21
|
+
for (const usage of usages) {
|
|
22
|
+
total.input += usage.input;
|
|
23
|
+
total.output += usage.output;
|
|
24
|
+
total.cacheRead += usage.cacheRead;
|
|
25
|
+
total.cacheWrite += usage.cacheWrite;
|
|
26
|
+
total.totalTokens += usage.totalTokens;
|
|
27
|
+
total.cost.input += usage.cost.input;
|
|
28
|
+
total.cost.output += usage.cost.output;
|
|
29
|
+
total.cost.cacheRead += usage.cost.cacheRead;
|
|
30
|
+
total.cost.cacheWrite += usage.cost.cacheWrite;
|
|
31
|
+
total.cost.total += usage.cost.total;
|
|
32
|
+
if (usage.reasoning !== void 0) total.reasoning = (total.reasoning ?? 0) + usage.reasoning;
|
|
33
|
+
if (usage.cacheWrite1h !== void 0) {
|
|
34
|
+
total.cacheWrite1h = (total.cacheWrite1h ?? 0) + usage.cacheWrite1h;
|
|
35
|
+
}
|
|
36
|
+
addAudioCounters(total, usage);
|
|
37
|
+
}
|
|
38
|
+
return total;
|
|
39
|
+
}
|
|
40
|
+
function failure(error) {
|
|
41
|
+
if (error instanceof VoiceRequestError) {
|
|
42
|
+
return { type: "error", reason: error.reason, error: error.message };
|
|
43
|
+
}
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
return { type: "error", reason: "error", error: message };
|
|
46
|
+
}
|
|
47
|
+
async function playSentence(speak, model, input, sentence, offset, options, stream, usages, state) {
|
|
48
|
+
let inner;
|
|
49
|
+
try {
|
|
50
|
+
inner = speak(model, { ...input, text: sentence }, options);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
stream.push(failure(error));
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
let terminal = false;
|
|
56
|
+
for await (const event of inner) {
|
|
57
|
+
if (options?.signal?.aborted && event.type !== "error" && event.type !== "done") {
|
|
58
|
+
stream.push({ type: "error", reason: "aborted", error: "aborted" });
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (event.type === "start") {
|
|
62
|
+
if (!state.started) stream.push(event);
|
|
63
|
+
state.started = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (event.type === "audio") {
|
|
67
|
+
stream.push({ ...event, span: shiftSpan(event.span, offset) });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (event.type === "done") {
|
|
71
|
+
usages.push(event.usage);
|
|
72
|
+
terminal = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
stream.push(event);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (terminal) return true;
|
|
79
|
+
try {
|
|
80
|
+
usages.push(await inner.result());
|
|
81
|
+
return true;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
stream.push(failure(error));
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function speakInSentences(speak, model, input, options) {
|
|
88
|
+
const stream = new SpeechEventStream();
|
|
89
|
+
void pump(speak, model, input, options, stream);
|
|
90
|
+
return stream;
|
|
91
|
+
}
|
|
92
|
+
async function pump(speak, model, input, options, stream) {
|
|
93
|
+
if (options?.signal?.aborted) {
|
|
94
|
+
stream.push({ type: "error", reason: "aborted", error: "aborted" });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const usages = [];
|
|
98
|
+
const state = { started: false };
|
|
99
|
+
let offset = 0;
|
|
100
|
+
let played = false;
|
|
101
|
+
try {
|
|
102
|
+
for await (const sentence of sentencesOf(input.text)) {
|
|
103
|
+
if (options?.signal?.aborted) {
|
|
104
|
+
stream.push({ type: "error", reason: "aborted", error: "aborted" });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
played = true;
|
|
108
|
+
const ok = await playSentence(
|
|
109
|
+
speak,
|
|
110
|
+
model,
|
|
111
|
+
input,
|
|
112
|
+
sentence,
|
|
113
|
+
offset,
|
|
114
|
+
options,
|
|
115
|
+
stream,
|
|
116
|
+
usages,
|
|
117
|
+
state
|
|
118
|
+
);
|
|
119
|
+
if (!ok) return;
|
|
120
|
+
offset += sentence.length;
|
|
121
|
+
}
|
|
122
|
+
if (!played) {
|
|
123
|
+
const text = typeof input.text === "string" ? input.text : "";
|
|
124
|
+
const ok = await playSentence(speak, model, input, text, 0, options, stream, usages, state);
|
|
125
|
+
if (!ok) return;
|
|
126
|
+
}
|
|
127
|
+
stream.push({ type: "done", usage: mergeUsage(usages) });
|
|
128
|
+
} catch (error) {
|
|
129
|
+
stream.push(failure(error));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
speakInSentences
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=chunk-speech.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/chunk-speech.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions, Usage } from '@earendil-works/pi-ai';\nimport type { TextSpan } from './audio.ts';\nimport { blankUsage } from './blank-usage.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel } from './models.ts';\nimport { sentencesOf } from './sentences.ts';\nimport {\n type SpeechApiProvider,\n type SpeechEvent,\n SpeechEventStream,\n type SpeechInput,\n} from './speech.ts';\nimport { addAudioCounters } from './usage.ts';\n\nfunction shiftSpan(span: TextSpan, offset: number): TextSpan {\n if ('entryId' in span) {\n return {\n entryId: span.entryId,\n contentIndex: span.contentIndex,\n start: span.start + offset,\n end: span.end + offset,\n };\n }\n return { itemId: span.itemId, start: span.start + offset, end: span.end + offset };\n}\n\nfunction mergeUsage(usages: Usage[]): Usage {\n const total = blankUsage();\n for (const usage of usages) {\n total.input += usage.input;\n total.output += usage.output;\n total.cacheRead += usage.cacheRead;\n total.cacheWrite += usage.cacheWrite;\n total.totalTokens += usage.totalTokens;\n total.cost.input += usage.cost.input;\n total.cost.output += usage.cost.output;\n total.cost.cacheRead += usage.cost.cacheRead;\n total.cost.cacheWrite += usage.cost.cacheWrite;\n total.cost.total += usage.cost.total;\n if (usage.reasoning !== undefined) total.reasoning = (total.reasoning ?? 0) + usage.reasoning;\n if (usage.cacheWrite1h !== undefined) {\n total.cacheWrite1h = (total.cacheWrite1h ?? 0) + usage.cacheWrite1h;\n }\n addAudioCounters(total, usage);\n }\n return total;\n}\n\nfunction failure(error: unknown): SpeechEvent {\n if (error instanceof VoiceRequestError) {\n return { type: 'error', reason: error.reason, error: error.message };\n }\n const message = error instanceof Error ? error.message : String(error);\n return { type: 'error', reason: 'error', error: message };\n}\n\nasync function playSentence(\n speak: SpeechApiProvider['speak'],\n model: SpeechModel<string>,\n input: SpeechInput,\n sentence: string,\n offset: number,\n options: ProviderRequestOptions | undefined,\n stream: SpeechEventStream,\n usages: Usage[],\n state: { started: boolean }\n): Promise<boolean> {\n let inner: SpeechEventStream;\n try {\n inner = speak(model, { ...input, text: sentence }, options);\n } catch (error) {\n stream.push(failure(error));\n return false;\n }\n let terminal = false;\n for await (const event of inner) {\n if (options?.signal?.aborted && event.type !== 'error' && event.type !== 'done') {\n stream.push({ type: 'error', reason: 'aborted', error: 'aborted' });\n return false;\n }\n if (event.type === 'start') {\n if (!state.started) stream.push(event);\n state.started = true;\n continue;\n }\n if (event.type === 'audio') {\n stream.push({ ...event, span: shiftSpan(event.span, offset) });\n continue;\n }\n if (event.type === 'done') {\n usages.push(event.usage);\n terminal = true;\n continue;\n }\n stream.push(event);\n return false;\n }\n if (terminal) return true;\n try {\n usages.push(await inner.result());\n return true;\n } catch (error) {\n stream.push(failure(error));\n return false;\n }\n}\n\nexport function speakInSentences(\n speak: SpeechApiProvider['speak'],\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const stream = new SpeechEventStream();\n void pump(speak, model, input, options, stream);\n return stream;\n}\n\nasync function pump(\n speak: SpeechApiProvider['speak'],\n model: SpeechModel<string>,\n input: SpeechInput,\n options: ProviderRequestOptions | undefined,\n stream: SpeechEventStream\n) {\n if (options?.signal?.aborted) {\n stream.push({ type: 'error', reason: 'aborted', error: 'aborted' });\n return;\n }\n const usages: Usage[] = [];\n const state = { started: false };\n let offset = 0;\n let played = false;\n try {\n for await (const sentence of sentencesOf(input.text)) {\n if (options?.signal?.aborted) {\n stream.push({ type: 'error', reason: 'aborted', error: 'aborted' });\n return;\n }\n played = true;\n const ok = await playSentence(\n speak,\n model,\n input,\n sentence,\n offset,\n options,\n stream,\n usages,\n state\n );\n if (!ok) return;\n offset += sentence.length;\n }\n if (!played) {\n const text = typeof input.text === 'string' ? input.text : '';\n const ok = await playSentence(speak, model, input, text, 0, options, stream, usages, state);\n if (!ok) return;\n }\n stream.push({ type: 'done', usage: mergeUsage(usages) });\n } catch (error) {\n stream.push(failure(error));\n }\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAElC,SAAS,mBAAmB;AAC5B;AAAA,EAGE;AAAA,OAEK;AACP,SAAS,wBAAwB;AAEjC,SAAS,UAAU,MAAgB,QAA0B;AAC3D,MAAI,aAAa,MAAM;AACrB,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,cAAc,KAAK;AAAA,MACnB,OAAO,KAAK,QAAQ;AAAA,MACpB,KAAK,KAAK,MAAM;AAAA,IAClB;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,KAAK,QAAQ,OAAO,KAAK,QAAQ,QAAQ,KAAK,KAAK,MAAM,OAAO;AACnF;AAEA,SAAS,WAAW,QAAwB;AAC1C,QAAM,QAAQ,WAAW;AACzB,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,MAAM;AACrB,UAAM,UAAU,MAAM;AACtB,UAAM,aAAa,MAAM;AACzB,UAAM,cAAc,MAAM;AAC1B,UAAM,eAAe,MAAM;AAC3B,UAAM,KAAK,SAAS,MAAM,KAAK;AAC/B,UAAM,KAAK,UAAU,MAAM,KAAK;AAChC,UAAM,KAAK,aAAa,MAAM,KAAK;AACnC,UAAM,KAAK,cAAc,MAAM,KAAK;AACpC,UAAM,KAAK,SAAS,MAAM,KAAK;AAC/B,QAAI,MAAM,cAAc,OAAW,OAAM,aAAa,MAAM,aAAa,KAAK,MAAM;AACpF,QAAI,MAAM,iBAAiB,QAAW;AACpC,YAAM,gBAAgB,MAAM,gBAAgB,KAAK,MAAM;AAAA,IACzD;AACA,qBAAiB,OAAO,KAAK;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,OAA6B;AAC5C,MAAI,iBAAiB,mBAAmB;AACtC,WAAO,EAAE,MAAM,SAAS,QAAQ,MAAM,QAAQ,OAAO,MAAM,QAAQ;AAAA,EACrE;AACA,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,SAAO,EAAE,MAAM,SAAS,QAAQ,SAAS,OAAO,QAAQ;AAC1D;AAEA,eAAe,aACb,OACA,OACA,OACA,UACA,QACA,SACA,QACA,QACA,OACkB;AAClB,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,OAAO,EAAE,GAAG,OAAO,MAAM,SAAS,GAAG,OAAO;AAAA,EAC5D,SAAS,OAAO;AACd,WAAO,KAAK,QAAQ,KAAK,CAAC;AAC1B,WAAO;AAAA,EACT;AACA,MAAI,WAAW;AACf,mBAAiB,SAAS,OAAO;AAC/B,QAAI,SAAS,QAAQ,WAAW,MAAM,SAAS,WAAW,MAAM,SAAS,QAAQ;AAC/E,aAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,WAAW,OAAO,UAAU,CAAC;AAClE,aAAO;AAAA,IACT;AACA,QAAI,MAAM,SAAS,SAAS;AAC1B,UAAI,CAAC,MAAM,QAAS,QAAO,KAAK,KAAK;AACrC,YAAM,UAAU;AAChB;AAAA,IACF;AACA,QAAI,MAAM,SAAS,SAAS;AAC1B,aAAO,KAAK,EAAE,GAAG,OAAO,MAAM,UAAU,MAAM,MAAM,MAAM,EAAE,CAAC;AAC7D;AAAA,IACF;AACA,QAAI,MAAM,SAAS,QAAQ;AACzB,aAAO,KAAK,MAAM,KAAK;AACvB,iBAAW;AACX;AAAA,IACF;AACA,WAAO,KAAK,KAAK;AACjB,WAAO;AAAA,EACT;AACA,MAAI,SAAU,QAAO;AACrB,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,OAAO,CAAC;AAChC,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,KAAK,QAAQ,KAAK,CAAC;AAC1B,WAAO;AAAA,EACT;AACF;AAEO,SAAS,iBACd,OACA,OACA,OACA,SACmB;AACnB,QAAM,SAAS,IAAI,kBAAkB;AACrC,OAAK,KAAK,OAAO,OAAO,OAAO,SAAS,MAAM;AAC9C,SAAO;AACT;AAEA,eAAe,KACb,OACA,OACA,OACA,SACA,QACA;AACA,MAAI,SAAS,QAAQ,SAAS;AAC5B,WAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,WAAW,OAAO,UAAU,CAAC;AAClE;AAAA,EACF;AACA,QAAM,SAAkB,CAAC;AACzB,QAAM,QAAQ,EAAE,SAAS,MAAM;AAC/B,MAAI,SAAS;AACb,MAAI,SAAS;AACb,MAAI;AACF,qBAAiB,YAAY,YAAY,MAAM,IAAI,GAAG;AACpD,UAAI,SAAS,QAAQ,SAAS;AAC5B,eAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,WAAW,OAAO,UAAU,CAAC;AAClE;AAAA,MACF;AACA,eAAS;AACT,YAAM,KAAK,MAAM;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,UAAI,CAAC,GAAI;AACT,gBAAU,SAAS;AAAA,IACrB;AACA,QAAI,CAAC,QAAQ;AACX,YAAM,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAC3D,YAAM,KAAK,MAAM,aAAa,OAAO,OAAO,OAAO,MAAM,GAAG,SAAS,QAAQ,QAAQ,KAAK;AAC1F,UAAI,CAAC,GAAI;AAAA,IACX;AACA,WAAO,KAAK,EAAE,MAAM,QAAQ,OAAO,WAAW,MAAM,EAAE,CAAC;AAAA,EACzD,SAAS,OAAO;AACd,WAAO,KAAK,QAAQ,KAAK,CAAC;AAAA,EAC5B;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/convert.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
const compressed = /* @__PURE__ */ new Set(["opus", "mp3", "flac"]);
|
|
2
|
+
const LAW_BIAS = 132;
|
|
3
|
+
const base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4
|
+
function byteAt(data, index) {
|
|
5
|
+
const byte = data[index];
|
|
6
|
+
return byte === void 0 ? 0 : byte;
|
|
7
|
+
}
|
|
8
|
+
function symbolAt(index) {
|
|
9
|
+
return base64Alphabet.charAt(index);
|
|
10
|
+
}
|
|
11
|
+
function encodeAudioBase64(data) {
|
|
12
|
+
let encoded = "";
|
|
13
|
+
for (let index = 0; index < data.length; index += 3) {
|
|
14
|
+
const second = data[index + 1];
|
|
15
|
+
const third = data[index + 2];
|
|
16
|
+
const triple = byteAt(data, index) << 16 | byteAt(data, index + 1) << 8 | byteAt(data, index + 2);
|
|
17
|
+
encoded += symbolAt(triple >> 18 & 63);
|
|
18
|
+
encoded += symbolAt(triple >> 12 & 63);
|
|
19
|
+
encoded += second === void 0 ? "=" : symbolAt(triple >> 6 & 63);
|
|
20
|
+
encoded += third === void 0 ? "=" : symbolAt(triple & 63);
|
|
21
|
+
}
|
|
22
|
+
return encoded;
|
|
23
|
+
}
|
|
24
|
+
function codeAt(value, index) {
|
|
25
|
+
const character = value[index];
|
|
26
|
+
if (character === void 0) return -1;
|
|
27
|
+
const code = base64Alphabet.indexOf(character);
|
|
28
|
+
if (code < 0) throw new Error("Invalid base64 audio");
|
|
29
|
+
return code;
|
|
30
|
+
}
|
|
31
|
+
function sextet(code) {
|
|
32
|
+
return code < 0 ? 0 : code;
|
|
33
|
+
}
|
|
34
|
+
function decodeAudioBase64(value) {
|
|
35
|
+
const cleaned = value.replaceAll("=", "");
|
|
36
|
+
if (cleaned.length % 4 === 1) throw new Error("Invalid base64 audio");
|
|
37
|
+
const bytes = [];
|
|
38
|
+
for (let index = 0; index < cleaned.length; index += 4) {
|
|
39
|
+
const first = codeAt(cleaned, index);
|
|
40
|
+
const second = codeAt(cleaned, index + 1);
|
|
41
|
+
const third = codeAt(cleaned, index + 2);
|
|
42
|
+
const fourth = codeAt(cleaned, index + 3);
|
|
43
|
+
const triple = sextet(first) << 18 | sextet(second) << 12 | sextet(third) << 6 | sextet(fourth);
|
|
44
|
+
bytes.push(triple >> 16 & 255);
|
|
45
|
+
if (third >= 0) bytes.push(triple >> 8 & 255);
|
|
46
|
+
if (fourth >= 0) bytes.push(triple & 255);
|
|
47
|
+
}
|
|
48
|
+
return Uint8Array.from(bytes);
|
|
49
|
+
}
|
|
50
|
+
function convertAudio(chunk, format) {
|
|
51
|
+
checkFormat(chunk.format);
|
|
52
|
+
checkFormat(format);
|
|
53
|
+
if (sameFormat(chunk.format, format)) {
|
|
54
|
+
return { data: chunk.data.slice(), format, timestamp: chunk.timestamp };
|
|
55
|
+
}
|
|
56
|
+
if (compressed.has(chunk.format.encoding) || compressed.has(format.encoding)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Cannot convert ${chunk.format.encoding} to ${format.encoding} without a codec`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const decoded = decode(chunk.data, chunk.format);
|
|
62
|
+
const mixed = mix(decoded.channels, format.channels);
|
|
63
|
+
const resampled = mixed.map(
|
|
64
|
+
(channel) => resample(channel, decoded.sampleRate, format.sampleRate)
|
|
65
|
+
);
|
|
66
|
+
return {
|
|
67
|
+
data: encode(resampled, format),
|
|
68
|
+
format,
|
|
69
|
+
timestamp: chunk.timestamp
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function* convertAudioSource(source, format) {
|
|
73
|
+
for await (const chunk of source) yield convertAudio(chunk, format);
|
|
74
|
+
}
|
|
75
|
+
function sameFormat(left, right) {
|
|
76
|
+
return left.encoding === right.encoding && left.sampleRate === right.sampleRate && left.channels === right.channels;
|
|
77
|
+
}
|
|
78
|
+
function checkFormat(format) {
|
|
79
|
+
if (format.channels !== 1 && format.channels !== 2) {
|
|
80
|
+
throw new Error(`Unsupported channel count ${String(format.channels)}`);
|
|
81
|
+
}
|
|
82
|
+
if (!Number.isFinite(format.sampleRate) || format.sampleRate <= 0) {
|
|
83
|
+
throw new Error(`Unsupported sample rate ${String(format.sampleRate)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function decode(data, format) {
|
|
87
|
+
if (format.encoding === "wav") return decodeWav(data);
|
|
88
|
+
return { channels: decodePcm(data, format), sampleRate: format.sampleRate };
|
|
89
|
+
}
|
|
90
|
+
function decodePcm(data, format) {
|
|
91
|
+
const width = sampleWidth(format.encoding);
|
|
92
|
+
const frames = Math.floor(data.byteLength / (width * format.channels));
|
|
93
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
94
|
+
const channels = Array.from({ length: format.channels }, () => new Float32Array(frames));
|
|
95
|
+
for (let frame = 0; frame < frames; frame++) {
|
|
96
|
+
for (let channel = 0; channel < format.channels; channel++) {
|
|
97
|
+
const offset = (frame * format.channels + channel) * width;
|
|
98
|
+
channels[channel][frame] = readSample(view, offset, format.encoding);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return channels;
|
|
102
|
+
}
|
|
103
|
+
function sampleWidth(encoding) {
|
|
104
|
+
if (encoding === "pcm16") return 2;
|
|
105
|
+
if (encoding === "pcmf32") return 4;
|
|
106
|
+
if (encoding === "mulaw" || encoding === "alaw") return 1;
|
|
107
|
+
throw new Error(`Unsupported audio encoding ${encoding}`);
|
|
108
|
+
}
|
|
109
|
+
function readSample(view, offset, encoding) {
|
|
110
|
+
if (encoding === "pcm16") return int16ToFloat(view.getInt16(offset, true));
|
|
111
|
+
if (encoding === "pcmf32") return finite(view.getFloat32(offset, true));
|
|
112
|
+
if (encoding === "mulaw") return int16ToFloat(mulawToLinear(view.getUint8(offset)));
|
|
113
|
+
return int16ToFloat(alawToLinear(view.getUint8(offset)));
|
|
114
|
+
}
|
|
115
|
+
function encode(channels, format) {
|
|
116
|
+
if (format.encoding === "wav") {
|
|
117
|
+
const pcm = encode(channels, { ...format, encoding: "pcm16" });
|
|
118
|
+
return wavBytes(pcm, format.sampleRate, format.channels);
|
|
119
|
+
}
|
|
120
|
+
const width = sampleWidth(format.encoding);
|
|
121
|
+
const frames = channels[0]?.length ?? 0;
|
|
122
|
+
const bytes = new Uint8Array(frames * format.channels * width);
|
|
123
|
+
const view = new DataView(bytes.buffer);
|
|
124
|
+
for (let frame = 0; frame < frames; frame++) {
|
|
125
|
+
for (let channel = 0; channel < format.channels; channel++) {
|
|
126
|
+
const offset = (frame * format.channels + channel) * width;
|
|
127
|
+
writeSample(view, offset, format.encoding, channels[channel]?.[frame] ?? 0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return bytes;
|
|
131
|
+
}
|
|
132
|
+
function writeSample(view, offset, encoding, sample) {
|
|
133
|
+
const unit = finite(sample);
|
|
134
|
+
if (encoding === "pcm16") view.setInt16(offset, floatToInt16(unit), true);
|
|
135
|
+
else if (encoding === "pcmf32") view.setFloat32(offset, unit, true);
|
|
136
|
+
else if (encoding === "mulaw") view.setUint8(offset, linearToMulaw(floatToInt16(unit)));
|
|
137
|
+
else view.setUint8(offset, linearToAlaw(floatToInt16(unit)));
|
|
138
|
+
}
|
|
139
|
+
function mix(channels, target) {
|
|
140
|
+
const length = channels[0]?.length ?? 0;
|
|
141
|
+
if (channels.length === target) return channels;
|
|
142
|
+
if (target === 1) {
|
|
143
|
+
const mixed = new Float32Array(length);
|
|
144
|
+
if (channels.length === 0) return [mixed];
|
|
145
|
+
for (let index = 0; index < length; index++) {
|
|
146
|
+
let sum = 0;
|
|
147
|
+
for (const channel of channels) sum += channel[index] ?? 0;
|
|
148
|
+
mixed[index] = sum / channels.length;
|
|
149
|
+
}
|
|
150
|
+
return [mixed];
|
|
151
|
+
}
|
|
152
|
+
const left = channels[0] ?? new Float32Array(length);
|
|
153
|
+
return [left, channels[1] ?? left];
|
|
154
|
+
}
|
|
155
|
+
function resample(input, inRate, outRate) {
|
|
156
|
+
if (inRate === outRate || input.length === 0) return input;
|
|
157
|
+
const outLength = Math.max(1, Math.round(input.length * outRate / inRate));
|
|
158
|
+
const output = new Float32Array(outLength);
|
|
159
|
+
const scale = inRate / outRate;
|
|
160
|
+
const last = input.length - 1;
|
|
161
|
+
for (let index = 0; index < outLength; index++) {
|
|
162
|
+
const position = index * scale;
|
|
163
|
+
const cursor = Math.min(Math.floor(position), last);
|
|
164
|
+
const next = Math.min(cursor + 1, last);
|
|
165
|
+
const fraction = position - cursor;
|
|
166
|
+
const left = input[cursor] ?? 0;
|
|
167
|
+
const right = input[next] ?? 0;
|
|
168
|
+
output[index] = left + (right - left) * fraction;
|
|
169
|
+
}
|
|
170
|
+
return output;
|
|
171
|
+
}
|
|
172
|
+
function finite(sample) {
|
|
173
|
+
return Number.isFinite(sample) ? sample : 0;
|
|
174
|
+
}
|
|
175
|
+
function floatToInt16(sample) {
|
|
176
|
+
const clamped = Math.max(-1, Math.min(1, sample));
|
|
177
|
+
const scaled = clamped < 0 ? clamped * 32768 : clamped * 32767;
|
|
178
|
+
return Math.max(-32768, Math.min(32767, Math.round(scaled)));
|
|
179
|
+
}
|
|
180
|
+
function int16ToFloat(sample) {
|
|
181
|
+
return sample < 0 ? sample / 32768 : sample / 32767;
|
|
182
|
+
}
|
|
183
|
+
function mulawToLinear(mulaw) {
|
|
184
|
+
const value = ~mulaw;
|
|
185
|
+
const exponent = (value & 112) >> 4;
|
|
186
|
+
let sample = ((value & 15) << 3) + LAW_BIAS;
|
|
187
|
+
sample <<= exponent;
|
|
188
|
+
return (value & 128) !== 0 ? LAW_BIAS - sample : sample - LAW_BIAS;
|
|
189
|
+
}
|
|
190
|
+
function alawToLinear(alaw) {
|
|
191
|
+
const value = alaw ^ 85;
|
|
192
|
+
const exponent = (value & 112) >> 4;
|
|
193
|
+
const mantissa = value & 15;
|
|
194
|
+
const sample = exponent === 0 ? mantissa + mantissa + 1 << 3 : mantissa + mantissa + 1 + 32 << exponent + 2;
|
|
195
|
+
return (value & 128) !== 0 ? sample : -sample;
|
|
196
|
+
}
|
|
197
|
+
function buildLawTable(expand, mask) {
|
|
198
|
+
const table = new Uint8Array(16384);
|
|
199
|
+
table[8192] = mask;
|
|
200
|
+
let cursor = 1;
|
|
201
|
+
for (let code = 0; code < 127; code++) {
|
|
202
|
+
const left = expand(code ^ mask);
|
|
203
|
+
const right = expand(code + 1 ^ mask);
|
|
204
|
+
const limit = left + right + 4 >> 3;
|
|
205
|
+
for (; cursor < limit; cursor += 1) {
|
|
206
|
+
table[8192 - cursor] = (code ^ (mask ^ 128)) & 255;
|
|
207
|
+
table[8192 + cursor] = (code ^ mask) & 255;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (; cursor < 8192; cursor++) {
|
|
211
|
+
table[8192 - cursor] = (127 ^ (mask ^ 128)) & 255;
|
|
212
|
+
table[8192 + cursor] = (127 ^ mask) & 255;
|
|
213
|
+
}
|
|
214
|
+
table[0] = table[1] ?? mask;
|
|
215
|
+
return table;
|
|
216
|
+
}
|
|
217
|
+
const mulawTable = buildLawTable(mulawToLinear, 255);
|
|
218
|
+
const alawTable = buildLawTable(alawToLinear, 213);
|
|
219
|
+
function lawCode(table, pcm, fallback) {
|
|
220
|
+
return table[pcm + 32768 >> 2] ?? fallback;
|
|
221
|
+
}
|
|
222
|
+
function linearToMulaw(pcm) {
|
|
223
|
+
return lawCode(mulawTable, pcm, 255);
|
|
224
|
+
}
|
|
225
|
+
function linearToAlaw(pcm) {
|
|
226
|
+
return lawCode(alawTable, pcm, 213);
|
|
227
|
+
}
|
|
228
|
+
function decodeWav(data) {
|
|
229
|
+
if (data.byteLength < 44) throw new Error("Invalid WAV audio");
|
|
230
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
231
|
+
if (text(data, 0, 4) !== "RIFF" || text(data, 8, 4) !== "WAVE")
|
|
232
|
+
throw new Error("Invalid WAV audio");
|
|
233
|
+
let cursor = 12;
|
|
234
|
+
let format;
|
|
235
|
+
let payload;
|
|
236
|
+
while (cursor + 8 <= data.byteLength) {
|
|
237
|
+
const id = text(data, cursor, 4);
|
|
238
|
+
const size = view.getUint32(cursor + 4, true);
|
|
239
|
+
const start = cursor + 8;
|
|
240
|
+
const end = Math.min(start + size, data.byteLength);
|
|
241
|
+
if (id === "fmt " && end - start >= 16) {
|
|
242
|
+
format = {
|
|
243
|
+
audioFormat: view.getUint16(start, true),
|
|
244
|
+
channels: view.getUint16(start + 2, true),
|
|
245
|
+
sampleRate: view.getUint32(start + 4, true),
|
|
246
|
+
width: view.getUint16(start + 14, true) / 8
|
|
247
|
+
};
|
|
248
|
+
} else if (id === "data") payload = data.subarray(start, end);
|
|
249
|
+
cursor = start + size + size % 2;
|
|
250
|
+
}
|
|
251
|
+
if (!format || !payload) throw new Error("Invalid WAV audio");
|
|
252
|
+
if (format.audioFormat !== 1 || format.width !== 2) throw new Error("WAV audio must be PCM16");
|
|
253
|
+
if (format.channels !== 1 && format.channels !== 2) throw new Error("Invalid WAV audio");
|
|
254
|
+
return {
|
|
255
|
+
channels: decodePcm(payload, {
|
|
256
|
+
encoding: "pcm16",
|
|
257
|
+
sampleRate: format.sampleRate,
|
|
258
|
+
channels: format.channels
|
|
259
|
+
}),
|
|
260
|
+
sampleRate: format.sampleRate
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function wavBytes(pcm, sampleRate, channels) {
|
|
264
|
+
const bytes = new Uint8Array(44 + pcm.byteLength);
|
|
265
|
+
const view = new DataView(bytes.buffer);
|
|
266
|
+
bytes.set(ascii("RIFF"), 0);
|
|
267
|
+
view.setUint32(4, 36 + pcm.byteLength, true);
|
|
268
|
+
bytes.set(ascii("WAVE"), 8);
|
|
269
|
+
bytes.set(ascii("fmt "), 12);
|
|
270
|
+
view.setUint32(16, 16, true);
|
|
271
|
+
view.setUint16(20, 1, true);
|
|
272
|
+
view.setUint16(22, channels, true);
|
|
273
|
+
view.setUint32(24, sampleRate, true);
|
|
274
|
+
view.setUint32(28, sampleRate * channels * 2, true);
|
|
275
|
+
view.setUint16(32, channels * 2, true);
|
|
276
|
+
view.setUint16(34, 16, true);
|
|
277
|
+
bytes.set(ascii("data"), 36);
|
|
278
|
+
view.setUint32(40, pcm.byteLength, true);
|
|
279
|
+
bytes.set(pcm, 44);
|
|
280
|
+
return bytes;
|
|
281
|
+
}
|
|
282
|
+
function text(data, offset, length) {
|
|
283
|
+
return String.fromCharCode(...data.subarray(offset, offset + length));
|
|
284
|
+
}
|
|
285
|
+
function ascii(value) {
|
|
286
|
+
return Uint8Array.from(value, (character) => character.charCodeAt(0));
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
convertAudio,
|
|
290
|
+
convertAudioSource,
|
|
291
|
+
decodeAudioBase64,
|
|
292
|
+
encodeAudioBase64
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/convert.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AudioChunk, AudioEncoding, AudioFormat, AudioSource } from './audio.ts';\n\nconst compressed = new Set<AudioEncoding>(['opus', 'mp3', 'flac']);\nconst LAW_BIAS = 0x84;\n\nconst base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\nfunction byteAt(data: Uint8Array, index: number): number {\n const byte = data[index];\n return byte === undefined ? 0 : byte;\n}\n\nfunction symbolAt(index: number): string {\n return base64Alphabet.charAt(index);\n}\n\nexport function encodeAudioBase64(data: Uint8Array): string {\n let encoded = '';\n for (let index = 0; index < data.length; index += 3) {\n const second = data[index + 1];\n const third = data[index + 2];\n const triple =\n (byteAt(data, index) << 16) | (byteAt(data, index + 1) << 8) | byteAt(data, index + 2);\n encoded += symbolAt((triple >> 18) & 63);\n encoded += symbolAt((triple >> 12) & 63);\n encoded += second === undefined ? '=' : symbolAt((triple >> 6) & 63);\n encoded += third === undefined ? '=' : symbolAt(triple & 63);\n }\n return encoded;\n}\n\nfunction codeAt(value: string, index: number): number {\n const character = value[index];\n if (character === undefined) return -1;\n const code = base64Alphabet.indexOf(character);\n if (code < 0) throw new Error('Invalid base64 audio');\n return code;\n}\n\nfunction sextet(code: number): number {\n return code < 0 ? 0 : code;\n}\n\nexport function decodeAudioBase64(value: string): Uint8Array {\n const cleaned = value.replaceAll('=', '');\n if (cleaned.length % 4 === 1) throw new Error('Invalid base64 audio');\n const bytes = [];\n for (let index = 0; index < cleaned.length; index += 4) {\n const first = codeAt(cleaned, index);\n const second = codeAt(cleaned, index + 1);\n const third = codeAt(cleaned, index + 2);\n const fourth = codeAt(cleaned, index + 3);\n const triple =\n (sextet(first) << 18) | (sextet(second) << 12) | (sextet(third) << 6) | sextet(fourth);\n bytes.push((triple >> 16) & 0xff);\n if (third >= 0) bytes.push((triple >> 8) & 0xff);\n if (fourth >= 0) bytes.push(triple & 0xff);\n }\n return Uint8Array.from(bytes);\n}\n\nexport function convertAudio(chunk: AudioChunk, format: AudioFormat): AudioChunk {\n checkFormat(chunk.format);\n checkFormat(format);\n if (sameFormat(chunk.format, format)) {\n return { data: chunk.data.slice(), format, timestamp: chunk.timestamp };\n }\n if (compressed.has(chunk.format.encoding) || compressed.has(format.encoding)) {\n throw new Error(\n `Cannot convert ${chunk.format.encoding} to ${format.encoding} without a codec`\n );\n }\n const decoded = decode(chunk.data, chunk.format);\n const mixed = mix(decoded.channels, format.channels);\n const resampled = mixed.map((channel) =>\n resample(channel, decoded.sampleRate, format.sampleRate)\n );\n return {\n data: encode(resampled, format),\n format,\n timestamp: chunk.timestamp,\n };\n}\n\nexport async function* convertAudioSource(source: AudioSource, format: AudioFormat): AudioSource {\n for await (const chunk of source) yield convertAudio(chunk, format);\n}\n\nfunction sameFormat(left: AudioFormat, right: AudioFormat): boolean {\n return (\n left.encoding === right.encoding &&\n left.sampleRate === right.sampleRate &&\n left.channels === right.channels\n );\n}\n\nfunction checkFormat(format: AudioFormat): void {\n if (format.channels !== 1 && format.channels !== 2) {\n throw new Error(`Unsupported channel count ${String(format.channels)}`);\n }\n if (!Number.isFinite(format.sampleRate) || format.sampleRate <= 0) {\n throw new Error(`Unsupported sample rate ${String(format.sampleRate)}`);\n }\n}\n\ninterface Planar {\n channels: Float32Array[];\n sampleRate: number;\n}\n\nfunction decode(data: Uint8Array, format: AudioFormat): Planar {\n if (format.encoding === 'wav') return decodeWav(data);\n return { channels: decodePcm(data, format), sampleRate: format.sampleRate };\n}\n\nfunction decodePcm(data: Uint8Array, format: AudioFormat): Float32Array[] {\n const width = sampleWidth(format.encoding);\n const frames = Math.floor(data.byteLength / (width * format.channels));\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n const channels = Array.from({ length: format.channels }, () => new Float32Array(frames));\n for (let frame = 0; frame < frames; frame++) {\n for (let channel = 0; channel < format.channels; channel++) {\n const offset = (frame * format.channels + channel) * width;\n channels[channel][frame] = readSample(view, offset, format.encoding);\n }\n }\n return channels;\n}\n\nfunction sampleWidth(encoding: AudioEncoding): number {\n if (encoding === 'pcm16') return 2;\n if (encoding === 'pcmf32') return 4;\n if (encoding === 'mulaw' || encoding === 'alaw') return 1;\n throw new Error(`Unsupported audio encoding ${encoding}`);\n}\n\nfunction readSample(view: DataView, offset: number, encoding: AudioEncoding): number {\n if (encoding === 'pcm16') return int16ToFloat(view.getInt16(offset, true));\n if (encoding === 'pcmf32') return finite(view.getFloat32(offset, true));\n if (encoding === 'mulaw') return int16ToFloat(mulawToLinear(view.getUint8(offset)));\n return int16ToFloat(alawToLinear(view.getUint8(offset)));\n}\n\nfunction encode(channels: Float32Array[], format: AudioFormat): Uint8Array {\n if (format.encoding === 'wav') {\n const pcm = encode(channels, { ...format, encoding: 'pcm16' });\n return wavBytes(pcm, format.sampleRate, format.channels);\n }\n const width = sampleWidth(format.encoding);\n const frames = channels[0]?.length ?? 0;\n const bytes = new Uint8Array(frames * format.channels * width);\n const view = new DataView(bytes.buffer);\n for (let frame = 0; frame < frames; frame++) {\n for (let channel = 0; channel < format.channels; channel++) {\n const offset = (frame * format.channels + channel) * width;\n writeSample(view, offset, format.encoding, channels[channel]?.[frame] ?? 0);\n }\n }\n return bytes;\n}\n\nfunction writeSample(\n view: DataView,\n offset: number,\n encoding: AudioEncoding,\n sample: number\n): void {\n const unit = finite(sample);\n if (encoding === 'pcm16') view.setInt16(offset, floatToInt16(unit), true);\n else if (encoding === 'pcmf32') view.setFloat32(offset, unit, true);\n else if (encoding === 'mulaw') view.setUint8(offset, linearToMulaw(floatToInt16(unit)));\n else view.setUint8(offset, linearToAlaw(floatToInt16(unit)));\n}\n\nfunction mix(channels: Float32Array[], target: 1 | 2): Float32Array[] {\n const length = channels[0]?.length ?? 0;\n if (channels.length === target) return channels;\n if (target === 1) {\n const mixed = new Float32Array(length);\n if (channels.length === 0) return [mixed];\n for (let index = 0; index < length; index++) {\n let sum = 0;\n for (const channel of channels) sum += channel[index] ?? 0;\n mixed[index] = sum / channels.length;\n }\n return [mixed];\n }\n const left = channels[0] ?? new Float32Array(length);\n return [left, channels[1] ?? left];\n}\n\nfunction resample(input: Float32Array, inRate: number, outRate: number): Float32Array {\n if (inRate === outRate || input.length === 0) return input;\n const outLength = Math.max(1, Math.round((input.length * outRate) / inRate));\n const output = new Float32Array(outLength);\n const scale = inRate / outRate;\n const last = input.length - 1;\n for (let index = 0; index < outLength; index++) {\n const position = index * scale;\n const cursor = Math.min(Math.floor(position), last);\n const next = Math.min(cursor + 1, last);\n const fraction = position - cursor;\n const left = input[cursor] ?? 0;\n const right = input[next] ?? 0;\n output[index] = left + (right - left) * fraction;\n }\n return output;\n}\n\nfunction finite(sample: number): number {\n return Number.isFinite(sample) ? sample : 0;\n}\n\nfunction floatToInt16(sample: number): number {\n const clamped = Math.max(-1, Math.min(1, sample));\n const scaled = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff;\n return Math.max(-0x8000, Math.min(0x7fff, Math.round(scaled)));\n}\n\nfunction int16ToFloat(sample: number): number {\n return sample < 0 ? sample / 0x8000 : sample / 0x7fff;\n}\n\nfunction mulawToLinear(mulaw: number): number {\n const value = ~mulaw;\n const exponent = (value & 0x70) >> 4;\n let sample = ((value & 0x0f) << 3) + LAW_BIAS;\n sample <<= exponent;\n return (value & 0x80) !== 0 ? LAW_BIAS - sample : sample - LAW_BIAS;\n}\n\nfunction alawToLinear(alaw: number): number {\n const value = alaw ^ 0x55;\n const exponent = (value & 0x70) >> 4;\n const mantissa = value & 0x0f;\n const sample =\n exponent === 0\n ? (mantissa + mantissa + 1) << 3\n : (mantissa + mantissa + 1 + 32) << (exponent + 2);\n return (value & 0x80) !== 0 ? sample : -sample;\n}\n\nfunction buildLawTable(expand: (code: number) => number, mask: number): Uint8Array {\n const table = new Uint8Array(16384);\n table[8192] = mask;\n let cursor = 1;\n for (let code = 0; code < 127; code++) {\n const left = expand(code ^ mask);\n const right = expand((code + 1) ^ mask);\n const limit = (left + right + 4) >> 3;\n for (; cursor < limit; cursor += 1) {\n table[8192 - cursor] = (code ^ (mask ^ 0x80)) & 0xff;\n table[8192 + cursor] = (code ^ mask) & 0xff;\n }\n }\n for (; cursor < 8192; cursor++) {\n table[8192 - cursor] = (127 ^ (mask ^ 0x80)) & 0xff;\n table[8192 + cursor] = (127 ^ mask) & 0xff;\n }\n table[0] = table[1] ?? mask;\n return table;\n}\n\nconst mulawTable = buildLawTable(mulawToLinear, 0xff);\nconst alawTable = buildLawTable(alawToLinear, 0xd5);\n\nfunction lawCode(table: Uint8Array, pcm: number, fallback: number): number {\n return table[(pcm + 32768) >> 2] ?? fallback;\n}\n\nfunction linearToMulaw(pcm: number): number {\n return lawCode(mulawTable, pcm, 0xff);\n}\n\nfunction linearToAlaw(pcm: number): number {\n return lawCode(alawTable, pcm, 0xd5);\n}\n\nfunction decodeWav(data: Uint8Array): Planar {\n if (data.byteLength < 44) throw new Error('Invalid WAV audio');\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n if (text(data, 0, 4) !== 'RIFF' || text(data, 8, 4) !== 'WAVE')\n throw new Error('Invalid WAV audio');\n let cursor = 12;\n let format:\n | { audioFormat: number; channels: number; sampleRate: number; width: number }\n | undefined;\n let payload: Uint8Array | undefined;\n while (cursor + 8 <= data.byteLength) {\n const id = text(data, cursor, 4);\n const size = view.getUint32(cursor + 4, true);\n const start = cursor + 8;\n const end = Math.min(start + size, data.byteLength);\n if (id === 'fmt ' && end - start >= 16) {\n format = {\n audioFormat: view.getUint16(start, true),\n channels: view.getUint16(start + 2, true),\n sampleRate: view.getUint32(start + 4, true),\n width: view.getUint16(start + 14, true) / 8,\n };\n } else if (id === 'data') payload = data.subarray(start, end);\n cursor = start + size + (size % 2);\n }\n if (!format || !payload) throw new Error('Invalid WAV audio');\n if (format.audioFormat !== 1 || format.width !== 2) throw new Error('WAV audio must be PCM16');\n if (format.channels !== 1 && format.channels !== 2) throw new Error('Invalid WAV audio');\n return {\n channels: decodePcm(payload, {\n encoding: 'pcm16',\n sampleRate: format.sampleRate,\n channels: format.channels,\n }),\n sampleRate: format.sampleRate,\n };\n}\n\nfunction wavBytes(pcm: Uint8Array, sampleRate: number, channels: number): Uint8Array {\n const bytes = new Uint8Array(44 + pcm.byteLength);\n const view = new DataView(bytes.buffer);\n bytes.set(ascii('RIFF'), 0);\n view.setUint32(4, 36 + pcm.byteLength, true);\n bytes.set(ascii('WAVE'), 8);\n bytes.set(ascii('fmt '), 12);\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, channels, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * channels * 2, true);\n view.setUint16(32, channels * 2, true);\n view.setUint16(34, 16, true);\n bytes.set(ascii('data'), 36);\n view.setUint32(40, pcm.byteLength, true);\n bytes.set(pcm, 44);\n return bytes;\n}\n\nfunction text(data: Uint8Array, offset: number, length: number): string {\n return String.fromCharCode(...data.subarray(offset, offset + length));\n}\n\nfunction ascii(value: string): Uint8Array {\n return Uint8Array.from(value, (character) => character.charCodeAt(0));\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { resolveVoiceAuth } from "./auth.js";
|
|
2
2
|
import { blankUsage } from "./blank-usage.js";
|
|
3
|
+
import {
|
|
4
|
+
convertAudio,
|
|
5
|
+
convertAudioSource,
|
|
6
|
+
decodeAudioBase64,
|
|
7
|
+
encodeAudioBase64
|
|
8
|
+
} from "./convert.js";
|
|
3
9
|
import { VoiceRequestError } from "./errors.js";
|
|
4
10
|
import {
|
|
5
11
|
getVoiceModel,
|
|
@@ -18,6 +24,7 @@ import {
|
|
|
18
24
|
import { SpeechEventStream } from "./speech.js";
|
|
19
25
|
import { TranscriptionEventStream } from "./transcription.js";
|
|
20
26
|
import { addAudioCounters, priceAudio } from "./usage.js";
|
|
27
|
+
import { energyVad } from "./vad.js";
|
|
21
28
|
export {
|
|
22
29
|
SpeechEventStream,
|
|
23
30
|
TranscriptionEventStream,
|
|
@@ -25,6 +32,11 @@ export {
|
|
|
25
32
|
addAudioCounters,
|
|
26
33
|
blankUsage,
|
|
27
34
|
connectRealtime,
|
|
35
|
+
convertAudio,
|
|
36
|
+
convertAudioSource,
|
|
37
|
+
decodeAudioBase64,
|
|
38
|
+
encodeAudioBase64,
|
|
39
|
+
energyVad,
|
|
28
40
|
getVoiceModel,
|
|
29
41
|
listVoiceModels,
|
|
30
42
|
listen,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeToken,\n} from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\n"],
|
|
5
|
-
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;AASzC,SAAS,kBAAkB,kBAAkB;",
|
|
4
|
+
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport {\n convertAudio,\n convertAudioSource,\n decodeAudioBase64,\n encodeAudioBase64,\n} from './convert.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeToken,\n} from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\nexport type { VadHook, VadUpdate } from './vad.ts';\nexport { energyVad } from './vad.ts';\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AASP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;AASzC,SAAS,kBAAkB,kBAAkB;AAE7C,SAAS,iBAAiB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/registry.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { speakInSentences } from "./chunk-speech.js";
|
|
2
|
+
import { listenWithVad } from "./vad-listen.js";
|
|
1
3
|
const transcriptionApis = /* @__PURE__ */ new Map();
|
|
2
4
|
const speechApis = /* @__PURE__ */ new Map();
|
|
3
5
|
const realtimeApis = /* @__PURE__ */ new Map();
|
|
@@ -33,11 +35,13 @@ function transcribe(model, input, options) {
|
|
|
33
35
|
function listen(model, audio, options) {
|
|
34
36
|
const listenModel = transcriptionApis.get(model.api)?.provider.listen;
|
|
35
37
|
if (!listenModel) throw missing(model.api);
|
|
38
|
+
if (model.turnDetection === "none") return listenWithVad(listenModel, model, audio, options);
|
|
36
39
|
return listenModel(model, audio, options);
|
|
37
40
|
}
|
|
38
41
|
function speak(model, input, options) {
|
|
39
42
|
const speakModel = speechApis.get(model.api)?.provider.speak;
|
|
40
43
|
if (!speakModel) throw missing(model.api);
|
|
44
|
+
if (!model.textStreaming) return speakInSentences(speakModel, model, input, options);
|
|
41
45
|
return speakModel(model, input, options);
|
|
42
46
|
}
|
|
43
47
|
function connectRealtime(model, context, options) {
|
package/dist/registry.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/registry.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.ts';\nimport type { RealtimeModel, SpeechModel, TranscriptionModel } from './models.ts';\nimport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeOptions,\n RealtimeSession,\n} from './realtime.ts';\nimport type { SpeechApiProvider, SpeechEventStream, SpeechInput } from './speech.ts';\nimport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEventStream,\n} from './transcription.ts';\n\ninterface TranscriptionEntry {\n provider: TranscriptionApiProvider;\n}\n\ninterface SpeechEntry {\n provider: SpeechApiProvider;\n}\n\ninterface RealtimeEntry {\n provider: RealtimeApiProvider;\n}\n\nconst transcriptionApis = new Map<string, TranscriptionEntry>();\nconst speechApis = new Map<string, SpeechEntry>();\nconst realtimeApis = new Map<string, RealtimeEntry>();\n\nfunction missing(api: string): Error {\n return new Error(`No API provider registered for api: ${api}`);\n}\n\nexport function registerTranscriptionApiProvider(provider: TranscriptionApiProvider): () => void {\n const entry = { provider };\n transcriptionApis.set(provider.api, entry);\n return () => {\n if (transcriptionApis.get(provider.api) === entry) transcriptionApis.delete(provider.api);\n };\n}\n\nexport function registerSpeechApiProvider(provider: SpeechApiProvider): () => void {\n const entry = { provider };\n speechApis.set(provider.api, entry);\n return () => {\n if (speechApis.get(provider.api) === entry) speechApis.delete(provider.api);\n };\n}\n\nexport function registerRealtimeApiProvider(provider: RealtimeApiProvider): () => void {\n const entry = { provider };\n realtimeApis.set(provider.api, entry);\n return () => {\n if (realtimeApis.get(provider.api) === entry) realtimeApis.delete(provider.api);\n };\n}\n\nexport function transcribe(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n const transcribeModel = transcriptionApis.get(model.api)?.provider.transcribe;\n if (!transcribeModel) return Promise.reject(missing(model.api));\n return transcribeModel(model, input, options);\n}\n\nexport function listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n): TranscriptionEventStream {\n const listenModel = transcriptionApis.get(model.api)?.provider.listen;\n if (!listenModel) throw missing(model.api);\n return listenModel(model, audio, options);\n}\n\nexport function speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const speakModel = speechApis.get(model.api)?.provider.speak;\n if (!speakModel) throw missing(model.api);\n return speakModel(model, input, options);\n}\n\nexport function connectRealtime(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options?: RealtimeOptions\n): Promise<RealtimeSession> {\n const connect = realtimeApis.get(model.api)?.provider.connect;\n if (!connect) return Promise.reject(missing(model.api));\n return connect(model, context, options);\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioSource } from './audio.ts';\nimport { speakInSentences } from './chunk-speech.ts';\nimport type { RealtimeModel, SpeechModel, TranscriptionModel } from './models.ts';\nimport type {\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeOptions,\n RealtimeSession,\n} from './realtime.ts';\nimport type { SpeechApiProvider, SpeechEventStream, SpeechInput } from './speech.ts';\nimport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEventStream,\n} from './transcription.ts';\nimport { listenWithVad } from './vad-listen.ts';\n\ninterface TranscriptionEntry {\n provider: TranscriptionApiProvider;\n}\n\ninterface SpeechEntry {\n provider: SpeechApiProvider;\n}\n\ninterface RealtimeEntry {\n provider: RealtimeApiProvider;\n}\n\nconst transcriptionApis = new Map<string, TranscriptionEntry>();\nconst speechApis = new Map<string, SpeechEntry>();\nconst realtimeApis = new Map<string, RealtimeEntry>();\n\nfunction missing(api: string): Error {\n return new Error(`No API provider registered for api: ${api}`);\n}\n\nexport function registerTranscriptionApiProvider(provider: TranscriptionApiProvider): () => void {\n const entry = { provider };\n transcriptionApis.set(provider.api, entry);\n return () => {\n if (transcriptionApis.get(provider.api) === entry) transcriptionApis.delete(provider.api);\n };\n}\n\nexport function registerSpeechApiProvider(provider: SpeechApiProvider): () => void {\n const entry = { provider };\n speechApis.set(provider.api, entry);\n return () => {\n if (speechApis.get(provider.api) === entry) speechApis.delete(provider.api);\n };\n}\n\nexport function registerRealtimeApiProvider(provider: RealtimeApiProvider): () => void {\n const entry = { provider };\n realtimeApis.set(provider.api, entry);\n return () => {\n if (realtimeApis.get(provider.api) === entry) realtimeApis.delete(provider.api);\n };\n}\n\nexport function transcribe(\n model: TranscriptionModel<string>,\n input: AudioInput,\n options?: ProviderRequestOptions\n): Promise<Transcript> {\n const transcribeModel = transcriptionApis.get(model.api)?.provider.transcribe;\n if (!transcribeModel) return Promise.reject(missing(model.api));\n return transcribeModel(model, input, options);\n}\n\nexport function listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n): TranscriptionEventStream {\n const listenModel = transcriptionApis.get(model.api)?.provider.listen;\n if (!listenModel) throw missing(model.api);\n if (model.turnDetection === 'none') return listenWithVad(listenModel, model, audio, options);\n return listenModel(model, audio, options);\n}\n\nexport function speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n): SpeechEventStream {\n const speakModel = speechApis.get(model.api)?.provider.speak;\n if (!speakModel) throw missing(model.api);\n if (!model.textStreaming) return speakInSentences(speakModel, model, input, options);\n return speakModel(model, input, options);\n}\n\nexport function connectRealtime(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options?: RealtimeOptions\n): Promise<RealtimeSession> {\n const connect = realtimeApis.get(model.api)?.provider.connect;\n if (!connect) return Promise.reject(missing(model.api));\n return connect(model, context, options);\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,wBAAwB;AAgBjC,SAAS,qBAAqB;AAc9B,MAAM,oBAAoB,oBAAI,IAAgC;AAC9D,MAAM,aAAa,oBAAI,IAAyB;AAChD,MAAM,eAAe,oBAAI,IAA2B;AAEpD,SAAS,QAAQ,KAAoB;AACnC,SAAO,IAAI,MAAM,uCAAuC,GAAG,EAAE;AAC/D;AAEO,SAAS,iCAAiC,UAAgD;AAC/F,QAAM,QAAQ,EAAE,SAAS;AACzB,oBAAkB,IAAI,SAAS,KAAK,KAAK;AACzC,SAAO,MAAM;AACX,QAAI,kBAAkB,IAAI,SAAS,GAAG,MAAM,MAAO,mBAAkB,OAAO,SAAS,GAAG;AAAA,EAC1F;AACF;AAEO,SAAS,0BAA0B,UAAyC;AACjF,QAAM,QAAQ,EAAE,SAAS;AACzB,aAAW,IAAI,SAAS,KAAK,KAAK;AAClC,SAAO,MAAM;AACX,QAAI,WAAW,IAAI,SAAS,GAAG,MAAM,MAAO,YAAW,OAAO,SAAS,GAAG;AAAA,EAC5E;AACF;AAEO,SAAS,4BAA4B,UAA2C;AACrF,QAAM,QAAQ,EAAE,SAAS;AACzB,eAAa,IAAI,SAAS,KAAK,KAAK;AACpC,SAAO,MAAM;AACX,QAAI,aAAa,IAAI,SAAS,GAAG,MAAM,MAAO,cAAa,OAAO,SAAS,GAAG;AAAA,EAChF;AACF;AAEO,SAAS,WACd,OACA,OACA,SACqB;AACrB,QAAM,kBAAkB,kBAAkB,IAAI,MAAM,GAAG,GAAG,SAAS;AACnE,MAAI,CAAC,gBAAiB,QAAO,QAAQ,OAAO,QAAQ,MAAM,GAAG,CAAC;AAC9D,SAAO,gBAAgB,OAAO,OAAO,OAAO;AAC9C;AAEO,SAAS,OACd,OACA,OACA,SAC0B;AAC1B,QAAM,cAAc,kBAAkB,IAAI,MAAM,GAAG,GAAG,SAAS;AAC/D,MAAI,CAAC,YAAa,OAAM,QAAQ,MAAM,GAAG;AACzC,MAAI,MAAM,kBAAkB,OAAQ,QAAO,cAAc,aAAa,OAAO,OAAO,OAAO;AAC3F,SAAO,YAAY,OAAO,OAAO,OAAO;AAC1C;AAEO,SAAS,MACd,OACA,OACA,SACmB;AACnB,QAAM,aAAa,WAAW,IAAI,MAAM,GAAG,GAAG,SAAS;AACvD,MAAI,CAAC,WAAY,OAAM,QAAQ,MAAM,GAAG;AACxC,MAAI,CAAC,MAAM,cAAe,QAAO,iBAAiB,YAAY,OAAO,OAAO,OAAO;AACnF,SAAO,WAAW,OAAO,OAAO,OAAO;AACzC;AAEO,SAAS,gBACd,OACA,SACA,SAC0B;AAC1B,QAAM,UAAU,aAAa,IAAI,MAAM,GAAG,GAAG,SAAS;AACtD,MAAI,CAAC,QAAS,QAAO,QAAQ,OAAO,QAAQ,MAAM,GAAG,CAAC;AACtD,SAAO,QAAQ,OAAO,SAAS,OAAO;AACxC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|