@ai-ecoverse/pi-audio 1.23.0 → 1.25.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 +4 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +2 -2
- package/dist/mixer.js +337 -0
- package/dist/mixer.js.map +7 -0
- package/dist/models.js.map +2 -2
- package/dist/node-host.js +132 -0
- package/dist/node-host.js.map +7 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/mixer.d.ts +15 -0
- package/dist/types/models.d.ts +1 -1
- package/dist/types/node-host.d.ts +34 -0
- package/dist/types/wavespeed/catalog.d.ts +1 -0
- package/dist/types/wavespeed/index.d.ts +1 -0
- package/dist/types/wavespeed/speak.d.ts +4 -0
- package/dist/types/wavespeed/transcribe.d.ts +4 -0
- package/dist/types/wavespeed/wire.d.ts +25 -0
- package/dist/types/worklet/convert.d.ts +9 -6
- package/dist/types/worklet/release.d.ts +13 -0
- package/dist/types/worklet/tap.d.ts +25 -0
- package/dist/wavespeed/catalog.js +60 -0
- package/dist/wavespeed/catalog.js.map +7 -0
- package/dist/wavespeed/index.js +5 -0
- package/dist/wavespeed/index.js.map +7 -0
- package/dist/wavespeed/speak.js +66 -0
- package/dist/wavespeed/speak.js.map +7 -0
- package/dist/wavespeed/transcribe.js +85 -0
- package/dist/wavespeed/transcribe.js.map +7 -0
- package/dist/wavespeed/wire.js +138 -0
- package/dist/wavespeed/wire.js.map +7 -0
- package/dist/worklet/convert.js +23 -0
- package/dist/worklet/convert.js.map +2 -2
- package/dist/worklet/release.js +42 -0
- package/dist/worklet/release.js.map +7 -0
- package/dist/worklet/tap.js +97 -0
- package/dist/worklet/tap.js.map +7 -0
- package/package.json +16 -2
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/wavespeed/wire.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport {\n applyPayload,\n endpoint,\n isRecord,\n numberField,\n requestHeaders,\n requestSignal,\n stringField,\n} from '../openai/wire.ts';\n\nconst terminal = new Set(['failed', 'cancelled', 'timeout', 'deleted']);\n\nexport function taskRecord(body: unknown): Record<string, unknown> {\n if (!isRecord(body) || Array.isArray(body)) throw new Error('WaveSpeed response was not JSON');\n const data = body.data;\n if (isRecord(data) && !Array.isArray(data)) return data;\n return body;\n}\n\nexport function errorText(text: string, status: number): string {\n if (text === '') return `HTTP ${status}`;\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n return text;\n }\n if (!isRecord(parsed) || Array.isArray(parsed)) return text;\n const message = stringField(parsed, 'message');\n if (message !== undefined && message !== '') return message;\n return text;\n}\n\nexport function isHttpUrl(value: string): boolean {\n if (value.startsWith('https://')) return true;\n if (value.startsWith('http://')) return true;\n return false;\n}\n\nexport function outputText(output: unknown): string | undefined {\n if (typeof output === 'string') return output;\n if (!isRecord(output) || Array.isArray(output)) return undefined;\n return stringField(output, 'text') ?? stringField(output, 'transcript');\n}\n\nexport function outputUrl(output: unknown): string | undefined {\n if (typeof output === 'string' && isHttpUrl(output)) return output;\n if (!isRecord(output) || Array.isArray(output)) return undefined;\n const url = stringField(output, 'url') ?? stringField(output, 'audio');\n if (url === undefined || !isHttpUrl(url)) return undefined;\n return url;\n}\n\nexport function downloadUrl(body: unknown): string {\n const record = taskRecord(body);\n const url = stringField(record, 'download_url') ?? stringField(record, 'url');\n if (url === undefined || url === '') throw new Error('WaveSpeed upload had no url');\n return url;\n}\n\nexport function durationSeconds(record: Record<string, unknown>): number {\n const direct = numberField(record, 'duration');\n if (direct !== undefined) return direct;\n return 0;\n}\n\nasync function waveFetch(\n model: { provider: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n url: string,\n body: BodyInit | undefined,\n contentType: string | undefined\n): Promise<unknown> {\n const headers = requestHeaders(model, options);\n if (contentType !== undefined) headers['Content-Type'] = contentType;\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(url, {\n method: body === undefined ? 'GET' : 'POST',\n headers,\n body,\n signal: requestSignal(options),\n });\n if (options?.onResponse !== undefined) {\n await options.onResponse({ status: response.status, headers: {} }, model as never);\n }\n if (!response.ok) {\n const text = await response.text();\n throw new Error(errorText(text, response.status));\n }\n const text = await response.text();\n if (text === '') throw new Error('WaveSpeed response was not JSON');\n try {\n return JSON.parse(text);\n } catch {\n throw new Error('WaveSpeed response was not JSON');\n }\n}\n\nfunction failure(record: Record<string, unknown>, status: string): Error {\n const error = stringField(record, 'error');\n if (error === undefined || error === '') return new Error(`WaveSpeed ${status}`);\n return new Error(error);\n}\n\nexport async function finishTask(\n model: { provider: string; baseUrl: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n payload: unknown,\n rejected: string\n): Promise<Record<string, unknown>> {\n const body = await applyPayload(payload, model, options);\n if (!isRecord(body) || Array.isArray(body)) throw new Error(rejected);\n let record = taskRecord(\n await waveFetch(\n model,\n options,\n endpoint(model.baseUrl, model.id),\n JSON.stringify(body),\n 'application/json'\n )\n );\n for (let attempt = 0; attempt < 9; attempt += 1) {\n const status = stringField(record, 'status') ?? '';\n if (status === 'completed') return record;\n if (terminal.has(status)) throw failure(record, status);\n if (attempt === 8) break;\n const id = stringField(record, 'id');\n if (id === undefined || id === '') throw new Error('WaveSpeed prediction had no id');\n record = taskRecord(\n await waveFetch(\n model,\n options,\n endpoint(model.baseUrl, `predictions/${id}/result`),\n undefined,\n undefined\n )\n );\n }\n throw new Error('WaveSpeed prediction timed out');\n}\n\nexport async function readHttp(\n model: { provider: string; headers?: Record<string, string>; id: string },\n options: ProviderRequestOptions | undefined,\n url: string\n): Promise<Response> {\n const headers = requestHeaders(model, options);\n const fetchImpl = options?.fetch ?? globalThis.fetch;\n const response = await fetchImpl(url, { headers, signal: requestSignal(options) });\n if (!response.ok) throw new Error(errorText('', response.status));\n return response;\n}\n\nexport { waveFetch };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["text"]
|
|
7
|
+
}
|
package/dist/worklet/convert.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { convertAudio } from "../convert.js";
|
|
2
|
+
import { isWorkletRelease, WORKLET_RELEASE } from "./release.js";
|
|
2
3
|
const AUDIO_CONVERT_PROCESSOR = "pi-audio-convert";
|
|
3
4
|
const scope = globalThis;
|
|
4
5
|
const WorkletBase = scope.AudioWorkletProcessor ?? class {
|
|
@@ -8,6 +9,8 @@ class AudioConvertProcessor extends WorkletBase {
|
|
|
8
9
|
#format;
|
|
9
10
|
#sampleRate;
|
|
10
11
|
#frames = 0;
|
|
12
|
+
#released = false;
|
|
13
|
+
#posted = false;
|
|
11
14
|
#pending = [];
|
|
12
15
|
constructor(options) {
|
|
13
16
|
super(options);
|
|
@@ -18,8 +21,21 @@ class AudioConvertProcessor extends WorkletBase {
|
|
|
18
21
|
};
|
|
19
22
|
const sampleRate = options?.processorOptions?.sampleRate;
|
|
20
23
|
this.#sampleRate = sampleRate !== void 0 && Number.isFinite(sampleRate) && sampleRate > 0 ? sampleRate : 48e3;
|
|
24
|
+
const port = this.port;
|
|
25
|
+
if (port) {
|
|
26
|
+
port.onmessage = (event) => {
|
|
27
|
+
if (isWorkletRelease(event.data)) this.release();
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
release() {
|
|
32
|
+
this.#released = true;
|
|
21
33
|
}
|
|
22
34
|
process(inputs) {
|
|
35
|
+
if (this.#released) {
|
|
36
|
+
this.#finish();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
23
39
|
const input = inputs[0];
|
|
24
40
|
const channels = [];
|
|
25
41
|
if (input) {
|
|
@@ -59,6 +75,13 @@ class AudioConvertProcessor extends WorkletBase {
|
|
|
59
75
|
});
|
|
60
76
|
return true;
|
|
61
77
|
}
|
|
78
|
+
#finish() {
|
|
79
|
+
if (this.#posted) return;
|
|
80
|
+
this.#posted = true;
|
|
81
|
+
const port = this.port;
|
|
82
|
+
if (!port) return;
|
|
83
|
+
port.postMessage({ type: WORKLET_RELEASE });
|
|
84
|
+
}
|
|
62
85
|
take() {
|
|
63
86
|
return this.#pending.splice(0);
|
|
64
87
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/worklet/convert.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AudioFormat } from '../audio.ts';\nimport { convertAudio } from '../convert.ts';\n\nexport const AUDIO_CONVERT_PROCESSOR = 'pi-audio-convert';\n\nexport interface AudioConvertProcessorOptions {\n processorOptions?: {\n format?: AudioFormat;\n sampleRate?: number;\n };\n}\n\ninterface WorkletScope {\n AudioWorkletProcessor?: new (\n options?: AudioConvertProcessorOptions\n ) => {\n port?:
|
|
5
|
-
"mappings": "AACA,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import type { AudioFormat } from '../audio.ts';\nimport { convertAudio } from '../convert.ts';\nimport { isWorkletRelease, WORKLET_RELEASE } from './release.ts';\n\nexport const AUDIO_CONVERT_PROCESSOR = 'pi-audio-convert';\n\nexport interface AudioConvertProcessorOptions {\n processorOptions?: {\n format?: AudioFormat;\n sampleRate?: number;\n };\n}\n\ninterface WorkletPort {\n postMessage(message: unknown): void;\n onmessage: ((event: { data?: unknown }) => void) | null;\n}\n\ninterface WorkletScope {\n AudioWorkletProcessor?: new (\n options?: AudioConvertProcessorOptions\n ) => {\n port?: WorkletPort;\n };\n registerProcessor?: (\n name: string,\n processor: new (options?: AudioConvertProcessorOptions) => object\n ) => void;\n}\n\nconst scope = globalThis as WorkletScope;\n\nconst WorkletBase =\n scope.AudioWorkletProcessor ??\n class {\n port?: WorkletPort;\n };\n\nexport class AudioConvertProcessor extends WorkletBase {\n readonly #format: AudioFormat;\n readonly #sampleRate: number;\n #frames = 0;\n #released = false;\n #posted = false;\n readonly #pending: Uint8Array[] = [];\n\n constructor(options?: AudioConvertProcessorOptions) {\n super(options);\n this.#format = options?.processorOptions?.format ?? {\n encoding: 'pcm16',\n sampleRate: 24000,\n channels: 1,\n };\n const sampleRate = options?.processorOptions?.sampleRate;\n this.#sampleRate =\n sampleRate !== undefined && Number.isFinite(sampleRate) && sampleRate > 0\n ? sampleRate\n : 48000;\n const port = this.port;\n if (port) {\n port.onmessage = (event) => {\n if (isWorkletRelease(event.data)) this.release();\n };\n }\n }\n\n release(): void {\n this.#released = true;\n }\n\n process(inputs: Array<ArrayLike<Float32Array | undefined> | undefined>): boolean {\n if (this.#released) {\n this.#finish();\n return true;\n }\n const input = inputs[0];\n const channels: Float32Array[] = [];\n if (input) {\n for (let index = 0; index < input.length; index++) {\n const channel = input[index];\n if (channel) channels.push(channel);\n }\n }\n const frames = channels[0]?.length ?? 0;\n if (channels.length === 0 || frames === 0) return true;\n const count = Math.min(channels.length, 2);\n const data = new Uint8Array(frames * count * 4);\n const view = new DataView(data.buffer);\n for (let frame = 0; frame < frames; frame++) {\n for (let channel = 0; channel < count; channel++) {\n view.setFloat32((frame * count + channel) * 4, channels[channel]?.[frame] ?? 0, true);\n }\n }\n const converted = convertAudio(\n {\n data,\n format: {\n encoding: 'pcmf32',\n sampleRate: this.#sampleRate,\n channels: count === 1 ? 1 : 2,\n },\n timestamp: (this.#frames / this.#sampleRate) * 1000,\n },\n this.#format\n );\n this.#frames += frames;\n this.#pending.push(converted.data);\n this.port?.postMessage({\n data: converted.data,\n format: converted.format,\n timestamp: converted.timestamp,\n });\n return true;\n }\n\n #finish(): void {\n if (this.#posted) return;\n this.#posted = true;\n const port = this.port;\n if (!port) return;\n port.postMessage({ type: WORKLET_RELEASE });\n }\n\n take(): Uint8Array[] {\n return this.#pending.splice(0);\n }\n}\n\nexport function registerAudioConvertWorklet(target: WorkletScope = scope): void {\n if (typeof target.registerProcessor !== 'function') return;\n target.registerProcessor(AUDIO_CONVERT_PROCESSOR, AudioConvertProcessor);\n}\n\nexport function audioConvertWorkletUrl(): string {\n return new URL(import.meta.url).href;\n}\n\nregisterAudioConvertWorklet();\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB,uBAAuB;AAE3C,MAAM,0BAA0B;AA0BvC,MAAM,QAAQ;AAEd,MAAM,cACJ,MAAM,yBACN,MAAM;AAAA,EACJ;AACF;AAEK,MAAM,8BAA8B,YAAY;AAAA,EAC5C;AAAA,EACA;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,UAAU;AAAA,EACD,WAAyB,CAAC;AAAA,EAEnC,YAAY,SAAwC;AAClD,UAAM,OAAO;AACb,SAAK,UAAU,SAAS,kBAAkB,UAAU;AAAA,MAClD,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,UAAU;AAAA,IACZ;AACA,UAAM,aAAa,SAAS,kBAAkB;AAC9C,SAAK,cACH,eAAe,UAAa,OAAO,SAAS,UAAU,KAAK,aAAa,IACpE,aACA;AACN,UAAM,OAAO,KAAK;AAClB,QAAI,MAAM;AACR,WAAK,YAAY,CAAC,UAAU;AAC1B,YAAI,iBAAiB,MAAM,IAAI,EAAG,MAAK,QAAQ;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,QAAQ,QAAyE;AAC/E,QAAI,KAAK,WAAW;AAClB,WAAK,QAAQ;AACb,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,WAA2B,CAAC;AAClC,QAAI,OAAO;AACT,eAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,cAAM,UAAU,MAAM,KAAK;AAC3B,YAAI,QAAS,UAAS,KAAK,OAAO;AAAA,MACpC;AAAA,IACF;AACA,UAAM,SAAS,SAAS,CAAC,GAAG,UAAU;AACtC,QAAI,SAAS,WAAW,KAAK,WAAW,EAAG,QAAO;AAClD,UAAM,QAAQ,KAAK,IAAI,SAAS,QAAQ,CAAC;AACzC,UAAM,OAAO,IAAI,WAAW,SAAS,QAAQ,CAAC;AAC9C,UAAM,OAAO,IAAI,SAAS,KAAK,MAAM;AACrC,aAAS,QAAQ,GAAG,QAAQ,QAAQ,SAAS;AAC3C,eAAS,UAAU,GAAG,UAAU,OAAO,WAAW;AAChD,aAAK,YAAY,QAAQ,QAAQ,WAAW,GAAG,SAAS,OAAO,IAAI,KAAK,KAAK,GAAG,IAAI;AAAA,MACtF;AAAA,IACF;AACA,UAAM,YAAY;AAAA,MAChB;AAAA,QACE;AAAA,QACA,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,YAAY,KAAK;AAAA,UACjB,UAAU,UAAU,IAAI,IAAI;AAAA,QAC9B;AAAA,QACA,WAAY,KAAK,UAAU,KAAK,cAAe;AAAA,MACjD;AAAA,MACA,KAAK;AAAA,IACP;AACA,SAAK,WAAW;AAChB,SAAK,SAAS,KAAK,UAAU,IAAI;AACjC,SAAK,MAAM,YAAY;AAAA,MACrB,MAAM,UAAU;AAAA,MAChB,QAAQ,UAAU;AAAA,MAClB,WAAW,UAAU;AAAA,IACvB,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,QAAS;AAClB,SAAK,UAAU;AACf,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AACX,SAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAAA,EAC5C;AAAA,EAEA,OAAqB;AACnB,WAAO,KAAK,SAAS,OAAO,CAAC;AAAA,EAC/B;AACF;AAEO,SAAS,4BAA4B,SAAuB,OAAa;AAC9E,MAAI,OAAO,OAAO,sBAAsB,WAAY;AACpD,SAAO,kBAAkB,yBAAyB,qBAAqB;AACzE;AAEO,SAAS,yBAAiC;AAC/C,SAAO,IAAI,IAAI,YAAY,GAAG,EAAE;AAClC;AAEA,4BAA4B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const WORKLET_RELEASE = "pi-audio-release";
|
|
2
|
+
function isWorkletRelease(data) {
|
|
3
|
+
if (typeof data !== "object" || data === null) return false;
|
|
4
|
+
if (!("type" in data)) return false;
|
|
5
|
+
return data.type === WORKLET_RELEASE;
|
|
6
|
+
}
|
|
7
|
+
function bindWorkletNode(node) {
|
|
8
|
+
let disconnected = false;
|
|
9
|
+
const disconnect = () => {
|
|
10
|
+
if (disconnected) return;
|
|
11
|
+
disconnected = true;
|
|
12
|
+
node.disconnect();
|
|
13
|
+
closePort(node.port);
|
|
14
|
+
};
|
|
15
|
+
const previous = node.port.onmessage;
|
|
16
|
+
node.port.onmessage = (event) => {
|
|
17
|
+
if (previous) previous(event);
|
|
18
|
+
if (isWorkletRelease(event.data)) disconnect();
|
|
19
|
+
};
|
|
20
|
+
let stopped = false;
|
|
21
|
+
return () => {
|
|
22
|
+
if (stopped) return;
|
|
23
|
+
stopped = true;
|
|
24
|
+
node.port.postMessage({ type: WORKLET_RELEASE });
|
|
25
|
+
disconnect();
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function closePort(port) {
|
|
29
|
+
const closer = port.close;
|
|
30
|
+
if (typeof closer !== "function") return;
|
|
31
|
+
try {
|
|
32
|
+
closer.call(port);
|
|
33
|
+
} catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
WORKLET_RELEASE,
|
|
39
|
+
bindWorkletNode,
|
|
40
|
+
isWorkletRelease
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=release.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/worklet/release.ts"],
|
|
4
|
+
"sourcesContent": ["export const WORKLET_RELEASE = 'pi-audio-release';\n\nexport interface WorkletPort {\n postMessage(message: unknown): void;\n onmessage: ((event: { data?: unknown }) => void) | null;\n}\n\nexport interface WorkletNodeLike {\n disconnect(): void;\n port: WorkletPort;\n}\n\nexport function isWorkletRelease(data: unknown): boolean {\n if (typeof data !== 'object' || data === null) return false;\n if (!('type' in data)) return false;\n return data.type === WORKLET_RELEASE;\n}\n\nexport function bindWorkletNode(node: WorkletNodeLike): () => void {\n let disconnected = false;\n const disconnect = () => {\n if (disconnected) return;\n disconnected = true;\n node.disconnect();\n closePort(node.port);\n };\n const previous = node.port.onmessage;\n node.port.onmessage = (event) => {\n if (previous) previous(event);\n if (isWorkletRelease(event.data)) disconnect();\n };\n let stopped = false;\n return () => {\n if (stopped) return;\n stopped = true;\n node.port.postMessage({ type: WORKLET_RELEASE });\n disconnect();\n };\n}\n\nfunction closePort(port: WorkletPort): void {\n const closer = (port as WorkletPort & { close?: () => void }).close;\n if (typeof closer !== 'function') return;\n try {\n closer.call(port);\n } catch {\n return;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,kBAAkB;AAYxB,SAAS,iBAAiB,MAAwB;AACvD,MAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,MAAI,EAAE,UAAU,MAAO,QAAO;AAC9B,SAAO,KAAK,SAAS;AACvB;AAEO,SAAS,gBAAgB,MAAmC;AACjE,MAAI,eAAe;AACnB,QAAM,aAAa,MAAM;AACvB,QAAI,aAAc;AAClB,mBAAe;AACf,SAAK,WAAW;AAChB,cAAU,KAAK,IAAI;AAAA,EACrB;AACA,QAAM,WAAW,KAAK,KAAK;AAC3B,OAAK,KAAK,YAAY,CAAC,UAAU;AAC/B,QAAI,SAAU,UAAS,KAAK;AAC5B,QAAI,iBAAiB,MAAM,IAAI,EAAG,YAAW;AAAA,EAC/C;AACA,MAAI,UAAU;AACd,SAAO,MAAM;AACX,QAAI,QAAS;AACb,cAAU;AACV,SAAK,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC/C,eAAW;AAAA,EACb;AACF;AAEA,SAAS,UAAU,MAAyB;AAC1C,QAAM,SAAU,KAA8C;AAC9D,MAAI,OAAO,WAAW,WAAY;AAClC,MAAI;AACF,WAAO,KAAK,IAAI;AAAA,EAClB,QAAQ;AACN;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isWorkletRelease, WORKLET_RELEASE } from "./release.js";
|
|
2
|
+
const AUDIO_TAP_PROCESSOR = "pi-audio-tap";
|
|
3
|
+
const scope = globalThis;
|
|
4
|
+
const registered = /* @__PURE__ */ new WeakSet();
|
|
5
|
+
function copyTap(inputs, outputs) {
|
|
6
|
+
const input = inputs[0] ?? [];
|
|
7
|
+
const output = outputs[0] ?? [];
|
|
8
|
+
for (let channel = 0; channel < output.length; channel += 1) {
|
|
9
|
+
const dst = output[channel];
|
|
10
|
+
if (dst === void 0) continue;
|
|
11
|
+
writeChannel(dst, input[channel] ?? input[0]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function tapSamples(inputs) {
|
|
15
|
+
const heard = inputs[0]?.[0];
|
|
16
|
+
if (heard === void 0 || heard.length === 0) return void 0;
|
|
17
|
+
const copy = new Float32Array(heard.length);
|
|
18
|
+
copy.set(heard);
|
|
19
|
+
return copy;
|
|
20
|
+
}
|
|
21
|
+
function registerAudioTapWorklet(target = scope) {
|
|
22
|
+
if (typeof target.registerProcessor !== "function") return;
|
|
23
|
+
if (registered.has(target)) return;
|
|
24
|
+
registered.add(target);
|
|
25
|
+
const Base = target.AudioWorkletProcessor ?? class {
|
|
26
|
+
port;
|
|
27
|
+
};
|
|
28
|
+
const processor = class AudioTapProcessor extends Base {
|
|
29
|
+
released = false;
|
|
30
|
+
#posted = false;
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
const port = this.port;
|
|
34
|
+
if (port) {
|
|
35
|
+
port.onmessage = (event) => {
|
|
36
|
+
if (isWorkletRelease(event.data)) this.released = true;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
process(inputs, outputs) {
|
|
41
|
+
if (this.released) {
|
|
42
|
+
this.#finish();
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
copyTap(inputs, outputs);
|
|
46
|
+
const samples = tapSamples(inputs);
|
|
47
|
+
if (samples) this.port?.postMessage({ samples });
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
#finish() {
|
|
51
|
+
if (this.#posted) return;
|
|
52
|
+
this.#posted = true;
|
|
53
|
+
const port = this.port;
|
|
54
|
+
if (!port) return;
|
|
55
|
+
port.postMessage({ type: WORKLET_RELEASE });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
target.registerProcessor(AUDIO_TAP_PROCESSOR, processor);
|
|
59
|
+
}
|
|
60
|
+
function audioTapWorkletUrl() {
|
|
61
|
+
return new URL(import.meta.url).href;
|
|
62
|
+
}
|
|
63
|
+
async function loadAudioTap(worklet, url = audioTapWorkletUrl()) {
|
|
64
|
+
try {
|
|
65
|
+
await worklet.addModule(url);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
try {
|
|
68
|
+
await worklet.addModule((next) => {
|
|
69
|
+
registerAudioTapWorklet(next);
|
|
70
|
+
});
|
|
71
|
+
} catch {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function writeChannel(dst, src) {
|
|
77
|
+
if (src === void 0) {
|
|
78
|
+
dst.fill(0);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (src.length === dst.length) {
|
|
82
|
+
dst.set(src);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const count = Math.min(src.length, dst.length);
|
|
86
|
+
for (let frame = 0; frame < count; frame += 1) dst[frame] = src[frame];
|
|
87
|
+
}
|
|
88
|
+
registerAudioTapWorklet();
|
|
89
|
+
export {
|
|
90
|
+
AUDIO_TAP_PROCESSOR,
|
|
91
|
+
audioTapWorkletUrl,
|
|
92
|
+
copyTap,
|
|
93
|
+
loadAudioTap,
|
|
94
|
+
registerAudioTapWorklet,
|
|
95
|
+
tapSamples
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=tap.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/worklet/tap.ts"],
|
|
4
|
+
"sourcesContent": ["import { isWorkletRelease, WORKLET_RELEASE } from './release.ts';\n\nexport const AUDIO_TAP_PROCESSOR = 'pi-audio-tap';\n\ninterface TapPort {\n postMessage(message: unknown): void;\n onmessage: ((event: { data?: unknown }) => void) | null;\n}\n\ninterface WorkletScope {\n AudioWorkletProcessor?: new () => { port?: TapPort };\n registerProcessor?: (name: string, processor: new () => object) => void;\n}\n\ninterface TapInputs {\n inputs: Array<ArrayLike<Float32Array | undefined> | undefined>;\n outputs: Array<ArrayLike<Float32Array | undefined> | undefined>;\n}\n\nconst scope = globalThis as WorkletScope;\nconst registered = new WeakSet<object>();\n\nexport function copyTap(inputs: TapInputs['inputs'], outputs: TapInputs['outputs']): void {\n const input = inputs[0] ?? [];\n const output = outputs[0] ?? [];\n for (let channel = 0; channel < output.length; channel += 1) {\n const dst = output[channel];\n if (dst === undefined) continue;\n writeChannel(dst, input[channel] ?? input[0]);\n }\n}\n\nexport function tapSamples(inputs: TapInputs['inputs']): Float32Array | undefined {\n const heard = inputs[0]?.[0];\n if (heard === undefined || heard.length === 0) return undefined;\n const copy = new Float32Array(heard.length);\n copy.set(heard);\n return copy;\n}\n\nexport function registerAudioTapWorklet(target: WorkletScope = scope): void {\n if (typeof target.registerProcessor !== 'function') return;\n if (registered.has(target)) return;\n registered.add(target);\n const Base =\n target.AudioWorkletProcessor ??\n class {\n port?: TapPort;\n };\n const processor = class AudioTapProcessor extends Base {\n released = false;\n #posted = false;\n\n constructor() {\n super();\n const port = this.port;\n if (port) {\n port.onmessage = (event) => {\n if (isWorkletRelease(event.data)) this.released = true;\n };\n }\n }\n\n process(inputs: TapInputs['inputs'], outputs: TapInputs['outputs']): boolean {\n if (this.released) {\n this.#finish();\n return true;\n }\n copyTap(inputs, outputs);\n const samples = tapSamples(inputs);\n if (samples) this.port?.postMessage({ samples });\n return true;\n }\n\n #finish(): void {\n if (this.#posted) return;\n this.#posted = true;\n const port = this.port;\n if (!port) return;\n port.postMessage({ type: WORKLET_RELEASE });\n }\n };\n target.registerProcessor(AUDIO_TAP_PROCESSOR, processor);\n}\n\nexport function audioTapWorkletUrl(): string {\n return new URL(import.meta.url).href;\n}\n\nexport async function loadAudioTap(\n worklet: { addModule(module: unknown): Promise<void> },\n url = audioTapWorkletUrl()\n): Promise<void> {\n try {\n await worklet.addModule(url);\n } catch (error) {\n try {\n await worklet.addModule((next: WorkletScope) => {\n registerAudioTapWorklet(next);\n });\n } catch {\n throw error;\n }\n }\n}\n\nfunction writeChannel(dst: Float32Array, src: Float32Array | undefined): void {\n if (src === undefined) {\n dst.fill(0);\n return;\n }\n if (src.length === dst.length) {\n dst.set(src);\n return;\n }\n const count = Math.min(src.length, dst.length);\n for (let frame = 0; frame < count; frame += 1) dst[frame] = src[frame] as number;\n}\n\nregisterAudioTapWorklet();\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB,uBAAuB;AAE3C,MAAM,sBAAsB;AAiBnC,MAAM,QAAQ;AACd,MAAM,aAAa,oBAAI,QAAgB;AAEhC,SAAS,QAAQ,QAA6B,SAAqC;AACxF,QAAM,QAAQ,OAAO,CAAC,KAAK,CAAC;AAC5B,QAAM,SAAS,QAAQ,CAAC,KAAK,CAAC;AAC9B,WAAS,UAAU,GAAG,UAAU,OAAO,QAAQ,WAAW,GAAG;AAC3D,UAAM,MAAM,OAAO,OAAO;AAC1B,QAAI,QAAQ,OAAW;AACvB,iBAAa,KAAK,MAAM,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EAC9C;AACF;AAEO,SAAS,WAAW,QAAuD;AAChF,QAAM,QAAQ,OAAO,CAAC,IAAI,CAAC;AAC3B,MAAI,UAAU,UAAa,MAAM,WAAW,EAAG,QAAO;AACtD,QAAM,OAAO,IAAI,aAAa,MAAM,MAAM;AAC1C,OAAK,IAAI,KAAK;AACd,SAAO;AACT;AAEO,SAAS,wBAAwB,SAAuB,OAAa;AAC1E,MAAI,OAAO,OAAO,sBAAsB,WAAY;AACpD,MAAI,WAAW,IAAI,MAAM,EAAG;AAC5B,aAAW,IAAI,MAAM;AACrB,QAAM,OACJ,OAAO,yBACP,MAAM;AAAA,IACJ;AAAA,EACF;AACF,QAAM,YAAY,MAAM,0BAA0B,KAAK;AAAA,IACrD,WAAW;AAAA,IACX,UAAU;AAAA,IAEV,cAAc;AACZ,YAAM;AACN,YAAM,OAAO,KAAK;AAClB,UAAI,MAAM;AACR,aAAK,YAAY,CAAC,UAAU;AAC1B,cAAI,iBAAiB,MAAM,IAAI,EAAG,MAAK,WAAW;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AAAA,IAEA,QAAQ,QAA6B,SAAwC;AAC3E,UAAI,KAAK,UAAU;AACjB,aAAK,QAAQ;AACb,eAAO;AAAA,MACT;AACA,cAAQ,QAAQ,OAAO;AACvB,YAAM,UAAU,WAAW,MAAM;AACjC,UAAI,QAAS,MAAK,MAAM,YAAY,EAAE,QAAQ,CAAC;AAC/C,aAAO;AAAA,IACT;AAAA,IAEA,UAAgB;AACd,UAAI,KAAK,QAAS;AAClB,WAAK,UAAU;AACf,YAAM,OAAO,KAAK;AAClB,UAAI,CAAC,KAAM;AACX,WAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAAA,IAC5C;AAAA,EACF;AACA,SAAO,kBAAkB,qBAAqB,SAAS;AACzD;AAEO,SAAS,qBAA6B;AAC3C,SAAO,IAAI,IAAI,YAAY,GAAG,EAAE;AAClC;AAEA,eAAsB,aACpB,SACA,MAAM,mBAAmB,GACV;AACf,MAAI;AACF,UAAM,QAAQ,UAAU,GAAG;AAAA,EAC7B,SAAS,OAAO;AACd,QAAI;AACF,YAAM,QAAQ,UAAU,CAAC,SAAuB;AAC9C,gCAAwB,IAAI;AAAA,MAC9B,CAAC;AAAA,IACH,QAAQ;AACN,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAEA,SAAS,aAAa,KAAmB,KAAqC;AAC5E,MAAI,QAAQ,QAAW;AACrB,QAAI,KAAK,CAAC;AACV;AAAA,EACF;AACA,MAAI,IAAI,WAAW,IAAI,QAAQ;AAC7B,QAAI,IAAI,GAAG;AACX;AAAA,EACF;AACA,QAAM,QAAQ,KAAK,IAAI,IAAI,QAAQ,IAAI,MAAM;AAC7C,WAAS,QAAQ,GAAG,QAAQ,OAAO,SAAS,EAAG,KAAI,KAAK,IAAI,IAAI,KAAK;AACvE;AAEA,wBAAwB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/pi-audio",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
"types": "./dist/types/cloudflare/index.d.ts",
|
|
67
67
|
"default": "./dist/cloudflare/index.js"
|
|
68
68
|
},
|
|
69
|
+
"./wavespeed": {
|
|
70
|
+
"types": "./dist/types/wavespeed/index.d.ts",
|
|
71
|
+
"default": "./dist/wavespeed/index.js"
|
|
72
|
+
},
|
|
69
73
|
"./durable": {
|
|
70
74
|
"types": "./dist/types/durable/index.d.ts",
|
|
71
75
|
"default": "./dist/durable/index.js"
|
|
@@ -105,6 +109,8 @@
|
|
|
105
109
|
"@biomejs/biome": "2.5.15",
|
|
106
110
|
"@earendil-works/pi-ai": "1.1.0",
|
|
107
111
|
"@earendil-works/pi-durable": "1.1.0",
|
|
112
|
+
"node-web-audio-api": "2.2.0",
|
|
113
|
+
"web-audio-api": "1.6.0",
|
|
108
114
|
"@types/node": "24.19.1",
|
|
109
115
|
"esbuild": "0.28.2",
|
|
110
116
|
"lefthook": "2.2.1",
|
|
@@ -114,11 +120,19 @@
|
|
|
114
120
|
},
|
|
115
121
|
"peerDependencies": {
|
|
116
122
|
"@earendil-works/pi-ai": "1.1.0",
|
|
117
|
-
"@earendil-works/pi-durable": "^1.1.0"
|
|
123
|
+
"@earendil-works/pi-durable": "^1.1.0",
|
|
124
|
+
"node-web-audio-api": "^2.2.0",
|
|
125
|
+
"web-audio-api": "^1.6.0"
|
|
118
126
|
},
|
|
119
127
|
"peerDependenciesMeta": {
|
|
120
128
|
"@earendil-works/pi-durable": {
|
|
121
129
|
"optional": true
|
|
130
|
+
},
|
|
131
|
+
"node-web-audio-api": {
|
|
132
|
+
"optional": true
|
|
133
|
+
},
|
|
134
|
+
"web-audio-api": {
|
|
135
|
+
"optional": true
|
|
122
136
|
}
|
|
123
137
|
}
|
|
124
138
|
}
|