@moxxy/plugin-tts-local 0.28.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/LICENSE +21 -0
- package/dist/host-client.d.ts +76 -0
- package/dist/host-client.d.ts.map +1 -0
- package/dist/host-client.js +154 -0
- package/dist/host-client.js.map +1 -0
- package/dist/host-protocol.d.ts +84 -0
- package/dist/host-protocol.d.ts.map +1 -0
- package/dist/host-protocol.js +57 -0
- package/dist/host-protocol.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/local-tts.d.ts +87 -0
- package/dist/local-tts.d.ts.map +1 -0
- package/dist/local-tts.js +204 -0
- package/dist/local-tts.js.map +1 -0
- package/dist/platform.d.ts +34 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +93 -0
- package/dist/platform.js.map +1 -0
- package/dist/sidecar.d.ts +18 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +86 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/voices.d.ts +57 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +71 -0
- package/dist/voices.js.map +1 -0
- package/dist/wav.d.ts +19 -0
- package/dist/wav.d.ts.map +1 -0
- package/dist/wav.js +62 -0
- package/dist/wav.js.map +1 -0
- package/package.json +67 -0
- package/src/host-client.test.ts +196 -0
- package/src/host-client.ts +205 -0
- package/src/host-protocol.test.ts +128 -0
- package/src/host-protocol.ts +127 -0
- package/src/index.ts +90 -0
- package/src/live.test.ts +56 -0
- package/src/local-tts.test.ts +195 -0
- package/src/local-tts.ts +268 -0
- package/src/platform.test.ts +77 -0
- package/src/platform.ts +106 -0
- package/src/sidecar.ts +97 -0
- package/src/voices.test.ts +106 -0
- package/src/voices.ts +135 -0
- package/src/wav.test.ts +57 -0
- package/src/wav.ts +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moxxy (moxxy.ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-side manager for the sherpa sidecar: lazy-spawn on first synthesize,
|
|
3
|
+
* a correlated request/reply protocol over the fork IPC channel, restart-once
|
|
4
|
+
* on a crash, and a clean shutdown. The `fork` itself is injectable (a
|
|
5
|
+
* {@link ForkLike}) so tests drive the whole protocol against a fake child with
|
|
6
|
+
* no real process.
|
|
7
|
+
*/
|
|
8
|
+
import type { HostRequest } from './host-protocol.js';
|
|
9
|
+
/** The minimal child-process surface the client drives — satisfied by a real
|
|
10
|
+
* `ChildProcess` and by a test fake. */
|
|
11
|
+
export interface ChildHandle {
|
|
12
|
+
send(message: unknown): boolean;
|
|
13
|
+
on(event: 'message', listener: (message: unknown) => void): this;
|
|
14
|
+
on(event: 'exit', listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
|
|
15
|
+
on(event: 'error', listener: (err: Error) => void): this;
|
|
16
|
+
kill(signal?: NodeJS.Signals): boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Fork the sidecar with the given env overrides. Injectable for tests. */
|
|
19
|
+
export type ForkLike = (modulePath: string, env: NodeJS.ProcessEnv) => ChildHandle;
|
|
20
|
+
/** Default fork: advanced serialization (Float32Array round-trip), an IPC
|
|
21
|
+
* channel, and inherited std streams so sherpa's own diagnostics reach the
|
|
22
|
+
* runner log. `env` already carries the platform loader-path var. */
|
|
23
|
+
export declare const defaultFork: ForkLike;
|
|
24
|
+
export interface HostClientOptions {
|
|
25
|
+
/** Absolute path to the built sidecar (`dist/sidecar.js`). */
|
|
26
|
+
readonly hostPath: string;
|
|
27
|
+
/** Env the child is forked with (platform loader path merged over process.env). */
|
|
28
|
+
readonly env: NodeJS.ProcessEnv;
|
|
29
|
+
/** Injected fork (tests). Defaults to {@link defaultFork}. */
|
|
30
|
+
readonly forkImpl?: ForkLike;
|
|
31
|
+
/** Per-request deadline. Default 120s (a long read-aloud on CPU). */
|
|
32
|
+
readonly requestTimeoutMs?: number;
|
|
33
|
+
/** Optional diagnostic sink. */
|
|
34
|
+
readonly log?: (msg: string) => void;
|
|
35
|
+
}
|
|
36
|
+
/** The slice of a host the synthesizer depends on — lets tests swap a fake. */
|
|
37
|
+
export interface HostClientLike {
|
|
38
|
+
synthesize(req: Omit<HostRequest, 'id' | 'type'>): Promise<{
|
|
39
|
+
samples: Float32Array;
|
|
40
|
+
sampleRate: number;
|
|
41
|
+
}>;
|
|
42
|
+
shutdown(): void;
|
|
43
|
+
}
|
|
44
|
+
/** Raised when the sidecar dies with a request in flight; drives the one retry. */
|
|
45
|
+
export declare class HostCrashError extends Error {
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
export declare class HostClient implements HostClientLike {
|
|
49
|
+
private readonly hostPath;
|
|
50
|
+
private readonly env;
|
|
51
|
+
private readonly forkImpl;
|
|
52
|
+
private readonly timeoutMs;
|
|
53
|
+
private readonly log;
|
|
54
|
+
private child;
|
|
55
|
+
private readonly pending;
|
|
56
|
+
private nextId;
|
|
57
|
+
private disposed;
|
|
58
|
+
constructor(opts: HostClientOptions);
|
|
59
|
+
/** Synthesize, restarting the sidecar ONCE if it crashes mid-request. */
|
|
60
|
+
synthesize(req: Omit<HostRequest, 'id' | 'type'>): Promise<{
|
|
61
|
+
samples: Float32Array;
|
|
62
|
+
sampleRate: number;
|
|
63
|
+
}>;
|
|
64
|
+
private send;
|
|
65
|
+
private ensureChild;
|
|
66
|
+
private onMessage;
|
|
67
|
+
private onExit;
|
|
68
|
+
private onError;
|
|
69
|
+
/** Resolve/reject one pending request, clearing its timer and map entry. */
|
|
70
|
+
private settle;
|
|
71
|
+
private rejectAll;
|
|
72
|
+
private killChild;
|
|
73
|
+
/** Kill the sidecar and fail any in-flight requests. Idempotent. */
|
|
74
|
+
shutdown(): void;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=host-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-client.d.ts","sourceRoot":"","sources":["../src/host-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-side manager for the sherpa sidecar: lazy-spawn on first synthesize,
|
|
3
|
+
* a correlated request/reply protocol over the fork IPC channel, restart-once
|
|
4
|
+
* on a crash, and a clean shutdown. The `fork` itself is injectable (a
|
|
5
|
+
* {@link ForkLike}) so tests drive the whole protocol against a fake child with
|
|
6
|
+
* no real process.
|
|
7
|
+
*/
|
|
8
|
+
import { fork } from 'node:child_process';
|
|
9
|
+
/** Default fork: advanced serialization (Float32Array round-trip), an IPC
|
|
10
|
+
* channel, and inherited std streams so sherpa's own diagnostics reach the
|
|
11
|
+
* runner log. `env` already carries the platform loader-path var. */
|
|
12
|
+
export const defaultFork = (modulePath, env) => fork(modulePath, [], {
|
|
13
|
+
serialization: 'advanced',
|
|
14
|
+
env,
|
|
15
|
+
execArgv: [],
|
|
16
|
+
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
|
17
|
+
});
|
|
18
|
+
/** Raised when the sidecar dies with a request in flight; drives the one retry. */
|
|
19
|
+
export class HostCrashError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'HostCrashError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
26
|
+
export class HostClient {
|
|
27
|
+
hostPath;
|
|
28
|
+
env;
|
|
29
|
+
forkImpl;
|
|
30
|
+
timeoutMs;
|
|
31
|
+
log;
|
|
32
|
+
child = null;
|
|
33
|
+
pending = new Map();
|
|
34
|
+
nextId = 1;
|
|
35
|
+
disposed = false;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.hostPath = opts.hostPath;
|
|
38
|
+
this.env = opts.env;
|
|
39
|
+
this.forkImpl = opts.forkImpl ?? defaultFork;
|
|
40
|
+
this.timeoutMs = opts.requestTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
41
|
+
this.log = opts.log ?? (() => { });
|
|
42
|
+
}
|
|
43
|
+
/** Synthesize, restarting the sidecar ONCE if it crashes mid-request. */
|
|
44
|
+
async synthesize(req) {
|
|
45
|
+
try {
|
|
46
|
+
return await this.send(req);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof HostCrashError && !this.disposed) {
|
|
50
|
+
this.log(`tts-local: sherpa sidecar crashed (${err.message}) — restarting once`);
|
|
51
|
+
return await this.send(req); // a second crash propagates
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
send(req) {
|
|
57
|
+
if (this.disposed)
|
|
58
|
+
return Promise.reject(new Error('tts-local host is shut down'));
|
|
59
|
+
const child = this.ensureChild();
|
|
60
|
+
const id = this.nextId++;
|
|
61
|
+
const message = { ...req, id, type: 'synthesize' };
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const timer = setTimeout(() => {
|
|
64
|
+
this.pending.delete(id);
|
|
65
|
+
// A hung synthesis is unrecoverable for this child — reset it so the
|
|
66
|
+
// next call starts fresh.
|
|
67
|
+
this.killChild();
|
|
68
|
+
reject(new Error(`tts-local synthesis timed out after ${this.timeoutMs} ms`));
|
|
69
|
+
}, this.timeoutMs);
|
|
70
|
+
timer.unref?.();
|
|
71
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
72
|
+
try {
|
|
73
|
+
child.send(message);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
this.settle(id, () => reject(err instanceof Error ? err : new Error(String(err))));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
ensureChild() {
|
|
81
|
+
if (this.child)
|
|
82
|
+
return this.child;
|
|
83
|
+
const child = this.forkImpl(this.hostPath, this.env);
|
|
84
|
+
this.child = child;
|
|
85
|
+
child.on('message', (msg) => this.onMessage(msg));
|
|
86
|
+
child.on('exit', (code, signal) => this.onExit(code, signal));
|
|
87
|
+
child.on('error', (err) => this.onError(err));
|
|
88
|
+
return child;
|
|
89
|
+
}
|
|
90
|
+
onMessage(msg) {
|
|
91
|
+
const reply = msg;
|
|
92
|
+
if (!reply || typeof reply !== 'object' || typeof reply.id !== 'number')
|
|
93
|
+
return;
|
|
94
|
+
const p = this.pending.get(reply.id);
|
|
95
|
+
if (!p)
|
|
96
|
+
return;
|
|
97
|
+
this.settle(reply.id, () => {
|
|
98
|
+
if (reply.ok)
|
|
99
|
+
p.resolve({ samples: reply.samples, sampleRate: reply.sampleRate });
|
|
100
|
+
else
|
|
101
|
+
p.reject(new Error(`tts-local sidecar ${reply.error.kind} error: ${reply.error.message}`));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
onExit(code, signal) {
|
|
105
|
+
this.child = null;
|
|
106
|
+
if (this.pending.size === 0)
|
|
107
|
+
return;
|
|
108
|
+
const why = `sidecar exited (code=${code ?? 'null'}, signal=${signal ?? 'null'})`;
|
|
109
|
+
this.rejectAll(new HostCrashError(why));
|
|
110
|
+
}
|
|
111
|
+
onError(err) {
|
|
112
|
+
this.child = null;
|
|
113
|
+
this.rejectAll(new HostCrashError(`sidecar spawn/runtime error: ${err.message}`));
|
|
114
|
+
}
|
|
115
|
+
/** Resolve/reject one pending request, clearing its timer and map entry. */
|
|
116
|
+
settle(id, run) {
|
|
117
|
+
const p = this.pending.get(id);
|
|
118
|
+
if (!p)
|
|
119
|
+
return;
|
|
120
|
+
if (p.timer)
|
|
121
|
+
clearTimeout(p.timer);
|
|
122
|
+
this.pending.delete(id);
|
|
123
|
+
run();
|
|
124
|
+
}
|
|
125
|
+
rejectAll(err) {
|
|
126
|
+
for (const [id, p] of this.pending) {
|
|
127
|
+
if (p.timer)
|
|
128
|
+
clearTimeout(p.timer);
|
|
129
|
+
this.pending.delete(id);
|
|
130
|
+
p.reject(err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
killChild() {
|
|
134
|
+
const child = this.child;
|
|
135
|
+
this.child = null;
|
|
136
|
+
if (child) {
|
|
137
|
+
try {
|
|
138
|
+
child.kill('SIGKILL');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* already gone */
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Kill the sidecar and fail any in-flight requests. Idempotent. */
|
|
146
|
+
shutdown() {
|
|
147
|
+
if (this.disposed)
|
|
148
|
+
return;
|
|
149
|
+
this.disposed = true;
|
|
150
|
+
this.rejectAll(new Error('tts-local host shut down'));
|
|
151
|
+
this.killChild();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=host-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-client.js","sourceRoot":"","sources":["../src/host-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tiny JSON message protocol spoken between the parent (host-client) and
|
|
3
|
+
* the forked sherpa sidecar, plus the pure per-message handler the sidecar
|
|
4
|
+
* runs. Kept free of `node:child_process` so BOTH sides can import it (the
|
|
5
|
+
* parent for the types, the child for the handler) without dragging fork
|
|
6
|
+
* plumbing into either bundle.
|
|
7
|
+
*
|
|
8
|
+
* Wire shape (over the fork IPC channel, `serialization: 'advanced'` so the
|
|
9
|
+
* Float32Array round-trips intact):
|
|
10
|
+
* parent → child: SynthesizeRequest
|
|
11
|
+
* child → parent: HostReply
|
|
12
|
+
*/
|
|
13
|
+
/** One synthesis request. Carries the full model config so the sidecar is
|
|
14
|
+
* stateless-per-message except for a model cache keyed by `voiceKey`. */
|
|
15
|
+
export interface SynthesizeRequest {
|
|
16
|
+
readonly id: number;
|
|
17
|
+
readonly type: 'synthesize';
|
|
18
|
+
/** Cache key for the loaded model (the absolute `.onnx` path). */
|
|
19
|
+
readonly voiceKey: string;
|
|
20
|
+
/** Absolute path to the VITS `.onnx` model. */
|
|
21
|
+
readonly model: string;
|
|
22
|
+
/** Absolute path to `tokens.txt`. */
|
|
23
|
+
readonly tokens: string;
|
|
24
|
+
/** Absolute path to the `espeak-ng-data` directory. */
|
|
25
|
+
readonly dataDir: string;
|
|
26
|
+
/** Inference thread count. */
|
|
27
|
+
readonly numThreads: number;
|
|
28
|
+
/** Compute provider (`cpu`). */
|
|
29
|
+
readonly provider: string;
|
|
30
|
+
/** Text to speak. */
|
|
31
|
+
readonly text: string;
|
|
32
|
+
/** Speaker id within the model (0 for single-speaker Piper voices). */
|
|
33
|
+
readonly sid: number;
|
|
34
|
+
/** Speaking-rate multiplier already clamped by the caller. */
|
|
35
|
+
readonly speed: number;
|
|
36
|
+
}
|
|
37
|
+
export type HostRequest = SynthesizeRequest;
|
|
38
|
+
export type HostErrorKind =
|
|
39
|
+
/** Model failed to load (bad/absent files, unsupported arch, addon dlopen). */
|
|
40
|
+
'init'
|
|
41
|
+
/** Synthesis itself threw. */
|
|
42
|
+
| 'runtime';
|
|
43
|
+
export type HostReply = {
|
|
44
|
+
readonly id: number;
|
|
45
|
+
readonly ok: true;
|
|
46
|
+
readonly sampleRate: number;
|
|
47
|
+
/** Mono PCM in [-1, 1]; structured-cloned intact over advanced IPC. */
|
|
48
|
+
readonly samples: Float32Array;
|
|
49
|
+
} | {
|
|
50
|
+
readonly id: number;
|
|
51
|
+
readonly ok: false;
|
|
52
|
+
readonly error: {
|
|
53
|
+
readonly message: string;
|
|
54
|
+
readonly kind: HostErrorKind;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/** The subset of the sherpa-onnx-node surface the sidecar uses. Declared here
|
|
58
|
+
* (rather than leaning on the addon's ambient types) so the handler is
|
|
59
|
+
* unit-testable with a fake module and typechecks without the native addon. */
|
|
60
|
+
export interface SherpaOfflineTts {
|
|
61
|
+
generateAsync(args: {
|
|
62
|
+
text: string;
|
|
63
|
+
sid: number;
|
|
64
|
+
speed: number;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
samples: Float32Array;
|
|
67
|
+
sampleRate: number;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface SherpaModule {
|
|
71
|
+
OfflineTts: new (config: unknown) => SherpaOfflineTts;
|
|
72
|
+
}
|
|
73
|
+
/** Lazily load the native sherpa module (so a dlopen failure surfaces as a
|
|
74
|
+
* classified reply, not a boot crash). */
|
|
75
|
+
export type LoadSherpa = () => SherpaModule;
|
|
76
|
+
export type HostMessageHandler = (req: HostRequest) => Promise<HostReply>;
|
|
77
|
+
/**
|
|
78
|
+
* Build the sidecar's message handler. Loads the sherpa module on first use and
|
|
79
|
+
* caches one `OfflineTts` per `voiceKey` so repeated calls on the same voice
|
|
80
|
+
* reuse the loaded model. Every failure becomes a typed `ok:false` reply —
|
|
81
|
+
* nothing throws out of `handle`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createMessageHandler(loadSherpa: LoadSherpa): HostMessageHandler;
|
|
84
|
+
//# sourceMappingURL=host-protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-protocol.d.ts","sourceRoot":"","sources":["../src/host-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;0EAC0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,+CAA+C;IAC/C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gCAAgC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE5C,MAAM,MAAM,aAAa;AACvB,+EAA+E;AAC7E,MAAM;AACR,8BAA8B;GAC5B,SAAS,CAAC;AAEd,MAAM,MAAM,SAAS,GACjB;IACE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC,GACD;IACE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC;CAC5E,CAAC;AAEN;;gFAEgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,IAAI,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,YAAY,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,KAAK,MAAM,EAAE,OAAO,KAAK,gBAAgB,CAAC;CACvD;AAED;2CAC2C;AAC3C,MAAM,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,kBAAkB,CAmC/E"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tiny JSON message protocol spoken between the parent (host-client) and
|
|
3
|
+
* the forked sherpa sidecar, plus the pure per-message handler the sidecar
|
|
4
|
+
* runs. Kept free of `node:child_process` so BOTH sides can import it (the
|
|
5
|
+
* parent for the types, the child for the handler) without dragging fork
|
|
6
|
+
* plumbing into either bundle.
|
|
7
|
+
*
|
|
8
|
+
* Wire shape (over the fork IPC channel, `serialization: 'advanced'` so the
|
|
9
|
+
* Float32Array round-trips intact):
|
|
10
|
+
* parent → child: SynthesizeRequest
|
|
11
|
+
* child → parent: HostReply
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Build the sidecar's message handler. Loads the sherpa module on first use and
|
|
15
|
+
* caches one `OfflineTts` per `voiceKey` so repeated calls on the same voice
|
|
16
|
+
* reuse the loaded model. Every failure becomes a typed `ok:false` reply —
|
|
17
|
+
* nothing throws out of `handle`.
|
|
18
|
+
*/
|
|
19
|
+
export function createMessageHandler(loadSherpa) {
|
|
20
|
+
const cache = new Map();
|
|
21
|
+
let mod = null;
|
|
22
|
+
return async function handle(req) {
|
|
23
|
+
let tts = cache.get(req.voiceKey);
|
|
24
|
+
if (!tts) {
|
|
25
|
+
try {
|
|
26
|
+
mod ??= loadSherpa();
|
|
27
|
+
tts = new mod.OfflineTts({
|
|
28
|
+
model: {
|
|
29
|
+
vits: { model: req.model, tokens: req.tokens, dataDir: req.dataDir },
|
|
30
|
+
numThreads: req.numThreads,
|
|
31
|
+
provider: req.provider,
|
|
32
|
+
debug: false,
|
|
33
|
+
},
|
|
34
|
+
maxNumSentences: 1,
|
|
35
|
+
});
|
|
36
|
+
cache.set(req.voiceKey, tts);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'init' } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const out = await tts.generateAsync({ text: req.text, sid: req.sid, speed: req.speed });
|
|
44
|
+
const samples = out.samples instanceof Float32Array
|
|
45
|
+
? out.samples
|
|
46
|
+
: Float32Array.from(out.samples);
|
|
47
|
+
return { id: req.id, ok: true, sampleRate: out.sampleRate, samples };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'runtime' } };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function errMsg(err) {
|
|
55
|
+
return err instanceof Error ? err.message : String(err);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=host-protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-protocol.js","sourceRoot":"","sources":["../src/host-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsEH;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAsB;IACzD,MAAM,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAClD,IAAI,GAAG,GAAwB,IAAI,CAAC;IAEpC,OAAO,KAAK,UAAU,MAAM,CAAC,GAAgB;QAC3C,IAAI,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,IAAI,CAAC;gBACH,GAAG,KAAK,UAAU,EAAE,CAAC;gBACrB,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC;oBACvB,KAAK,EAAE;wBACL,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE;wBACpE,UAAU,EAAE,GAAG,CAAC,UAAU;wBAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;wBACtB,KAAK,EAAE,KAAK;qBACb;oBACD,eAAe,EAAE,CAAC;iBACnB,CAAC,CAAC;gBACH,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC;YAClF,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;YACxF,MAAM,OAAO,GACX,GAAG,CAAC,OAAO,YAAY,YAAY;gBACjC,CAAC,CAAC,GAAG,CAAC,OAAO;gBACb,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,OAA4B,CAAC,CAAC;YAC1D,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;QACrF,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Plugin } from '@moxxy/sdk';
|
|
2
|
+
import { type LocalPiperOptions } from './local-tts.js';
|
|
3
|
+
export { LocalPiperSynthesizer, createLocalPiperSynthesizer, clampSpeed, shutdownLocalTts, LOCAL_PIPER_SYNTHESIZER_NAME, type LocalPiperOptions, type VoiceModelPaths, } from './local-tts.js';
|
|
4
|
+
export { encodeWav, WAV_HEADER_BYTES } from './wav.js';
|
|
5
|
+
export { VOICE_CATALOG, voiceIds, findVoice, routeVoice, requireVoice, DEFAULT_VOICE_ID, DEFAULT_POLISH_VOICE_ID, type VoiceEntry, type VoiceLanguage, } from './voices.js';
|
|
6
|
+
export { HostClient, type HostClientLike, type ForkLike, type ChildHandle } from './host-client.js';
|
|
7
|
+
export { sherpaPlatformPackage, resolveSherpaLibDir, sherpaEnv, libraryPathVar, } from './platform.js';
|
|
8
|
+
export interface BuildLocalTtsPluginOptions {
|
|
9
|
+
/** Build-time defaults / test seams for the synthesizer (injected
|
|
10
|
+
* `ensureModelImpl`, `hostFactory`, `fetchImpl`, `modelsDir`, `log`). */
|
|
11
|
+
readonly defaults?: LocalPiperOptions;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the @moxxy/plugin-tts-local plugin. Registers exactly one synthesizer,
|
|
15
|
+
* `local-piper`, backed by sherpa-onnx Piper voices running in a forked
|
|
16
|
+
* sidecar. Side-effect free at load: no model is downloaded and no process is
|
|
17
|
+
* spawned until the first `synthesize`. Per-activation config
|
|
18
|
+
* (`session.synthesizers.setActive('local-piper', { voice, polishVoice,
|
|
19
|
+
* numThreads })`) overrides the build-time defaults.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildLocalTtsPlugin(opts?: BuildLocalTtsPluginOptions): Plugin;
|
|
22
|
+
declare const _default: Plugin;
|
|
23
|
+
export default _default;
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmC,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAE1E,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,UAAU,EACV,gBAAgB,EAChB,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,aAAa,EACb,QAAQ,EACR,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,EACT,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,0BAA0B;IACzC;8EAC0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,0BAA+B,GAAG,MAAM,CAkBjF;;AAmBD,wBAAqC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { definePlugin, defineSynthesizer } from '@moxxy/sdk';
|
|
2
|
+
import { createLocalPiperSynthesizer, LOCAL_PIPER_SYNTHESIZER_NAME, shutdownLocalTts, } from './local-tts.js';
|
|
3
|
+
export { LocalPiperSynthesizer, createLocalPiperSynthesizer, clampSpeed, shutdownLocalTts, LOCAL_PIPER_SYNTHESIZER_NAME, } from './local-tts.js';
|
|
4
|
+
export { encodeWav, WAV_HEADER_BYTES } from './wav.js';
|
|
5
|
+
export { VOICE_CATALOG, voiceIds, findVoice, routeVoice, requireVoice, DEFAULT_VOICE_ID, DEFAULT_POLISH_VOICE_ID, } from './voices.js';
|
|
6
|
+
export { HostClient } from './host-client.js';
|
|
7
|
+
export { sherpaPlatformPackage, resolveSherpaLibDir, sherpaEnv, libraryPathVar, } from './platform.js';
|
|
8
|
+
/**
|
|
9
|
+
* Build the @moxxy/plugin-tts-local plugin. Registers exactly one synthesizer,
|
|
10
|
+
* `local-piper`, backed by sherpa-onnx Piper voices running in a forked
|
|
11
|
+
* sidecar. Side-effect free at load: no model is downloaded and no process is
|
|
12
|
+
* spawned until the first `synthesize`. Per-activation config
|
|
13
|
+
* (`session.synthesizers.setActive('local-piper', { voice, polishVoice,
|
|
14
|
+
* numThreads })`) overrides the build-time defaults.
|
|
15
|
+
*/
|
|
16
|
+
export function buildLocalTtsPlugin(opts = {}) {
|
|
17
|
+
const defaults = opts.defaults ?? {};
|
|
18
|
+
return definePlugin({
|
|
19
|
+
name: '@moxxy/plugin-tts-local',
|
|
20
|
+
version: '0.0.0',
|
|
21
|
+
synthesizers: [
|
|
22
|
+
defineSynthesizer({
|
|
23
|
+
name: LOCAL_PIPER_SYNTHESIZER_NAME,
|
|
24
|
+
displayName: 'Local (Piper — offline, EN+PL)',
|
|
25
|
+
// `create` runs lazily and may re-run (buildOnRead) — keep it cheap and
|
|
26
|
+
// side-effect free; the sidecar + download happen on first synthesize.
|
|
27
|
+
create: (ctx) => createLocalPiperSynthesizer({ ...defaults, ...configToOptions(ctx.config) }),
|
|
28
|
+
}),
|
|
29
|
+
],
|
|
30
|
+
// Kill every spawned sidecar when the session/runner shuts down.
|
|
31
|
+
hooks: { onShutdown: () => shutdownLocalTts() },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Narrow the untrusted per-activation config into typed options — only the
|
|
35
|
+
* known string `voice` / `polishVoice` and a positive-integer `numThreads` are
|
|
36
|
+
* honored so a malformed config can't break `create` or route to a bad voice
|
|
37
|
+
* (unknown ids are caught by `requireVoice` at construction). */
|
|
38
|
+
function configToOptions(config) {
|
|
39
|
+
const out = {};
|
|
40
|
+
if (typeof config.voice === 'string' && config.voice)
|
|
41
|
+
out.voice = config.voice;
|
|
42
|
+
if (typeof config.polishVoice === 'string' && config.polishVoice) {
|
|
43
|
+
out.polishVoice = config.polishVoice;
|
|
44
|
+
}
|
|
45
|
+
if (typeof config.numThreads === 'number' && Number.isInteger(config.numThreads) && config.numThreads > 0) {
|
|
46
|
+
out.numThreads = config.numThreads;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
// Discovery entry: `createPluginLoader` requires a default Plugin export.
|
|
51
|
+
export default buildLocalTtsPlugin();
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAe,MAAM,YAAY,CAAC;AAE1E,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,EAC5B,gBAAgB,GAEjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,UAAU,EACV,gBAAgB,EAChB,4BAA4B,GAG7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,aAAa,EACb,QAAQ,EACR,SAAS,EACT,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,uBAAuB,GAGxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAwD,MAAM,kBAAkB,CAAC;AACpG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,EACT,cAAc,GACf,MAAM,eAAe,CAAC;AAQvB;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAmC,EAAE;IACvE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,OAAO,YAAY,CAAC;QAClB,IAAI,EAAE,yBAAyB;QAC/B,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE;YACZ,iBAAiB,CAAC;gBAChB,IAAI,EAAE,4BAA4B;gBAClC,WAAW,EAAE,gCAAgC;gBAC7C,wEAAwE;gBACxE,uEAAuE;gBACvE,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CACd,2BAA2B,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;aAC/E,CAAC;SACH;QACD,iEAAiE;QACjE,KAAK,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED;;;kEAGkE;AAClE,SAAS,eAAe,CAAC,MAA+B;IACtD,MAAM,GAAG,GAAwE,EAAE,CAAC;IACpF,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK;QAAE,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC/E,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACjE,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC;IACvC,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC1G,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IACrC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,eAAe,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `local-piper` Synthesizer: fully local, on-device text-to-speech.
|
|
3
|
+
*
|
|
4
|
+
* On the first synthesis for a voice it downloads + verifies that voice's Piper
|
|
5
|
+
* model (once, via @moxxy/model-fetch), then hands text to the sherpa sidecar
|
|
6
|
+
* ({@link HostClient}) and wraps the returned samples as WAV. No key, no network
|
|
7
|
+
* at synthesis time. Language routing sends `pl*` requests to the configured
|
|
8
|
+
* Polish voice; an explicit `opts.voice` (a catalog id) overrides everything.
|
|
9
|
+
*
|
|
10
|
+
* Every collaborator is injectable (`ensureModelImpl`, `hostFactory`, `log`,
|
|
11
|
+
* `modelsDir`, `fetchImpl`) so the whole flow is unit-testable without the
|
|
12
|
+
* native addon or a real download.
|
|
13
|
+
*/
|
|
14
|
+
import { type SynthesizeOptions, type SynthesisResult, type Synthesizer } from '@moxxy/sdk';
|
|
15
|
+
import { ensureModel, type FetchLike } from '@moxxy/model-fetch';
|
|
16
|
+
import { type HostClientLike } from './host-client.js';
|
|
17
|
+
/** The single registered synthesizer name — surfaces show this in `set_voice`. */
|
|
18
|
+
export declare const LOCAL_PIPER_SYNTHESIZER_NAME = "local-piper";
|
|
19
|
+
/** Map a `SynthesizeOptions.rate` multiplier onto sherpa's `speed`, clamped.
|
|
20
|
+
* An absent / non-finite rate yields 1.0 (natural speed). */
|
|
21
|
+
export declare function clampSpeed(rate: number | undefined): number;
|
|
22
|
+
/** Resolved on-disk paths sherpa needs for one voice. */
|
|
23
|
+
export interface VoiceModelPaths {
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly tokens: string;
|
|
26
|
+
readonly dataDir: string;
|
|
27
|
+
}
|
|
28
|
+
export interface LocalPiperOptions {
|
|
29
|
+
/** Default (non-Polish) voice id. Default `en_US-amy-medium`. */
|
|
30
|
+
readonly voice?: string;
|
|
31
|
+
/** Voice used for `pl*` language requests. Default `pl_PL-gosia-medium`. */
|
|
32
|
+
readonly polishVoice?: string;
|
|
33
|
+
/** Inference threads. Default 2. */
|
|
34
|
+
readonly numThreads?: number;
|
|
35
|
+
/** Root for downloaded voices. Default `~/.moxxy/models/tts` (MOXXY_HOME-aware). */
|
|
36
|
+
readonly modelsDir?: string;
|
|
37
|
+
/** sherpa compute provider. Default `cpu`. */
|
|
38
|
+
readonly provider?: string;
|
|
39
|
+
/** Absolute path to the built sidecar entry. Default: `sidecar.js` beside
|
|
40
|
+
* this module (i.e. `dist/sidecar.js` in a published build). Overridable so
|
|
41
|
+
* the source-run live test can point at the compiled sidecar. */
|
|
42
|
+
readonly sidecarPath?: string;
|
|
43
|
+
/** Injected model-ensure (tests). Defaults to @moxxy/model-fetch `ensureModel`. */
|
|
44
|
+
readonly ensureModelImpl?: typeof ensureModel;
|
|
45
|
+
/** Injected `fetch`, threaded into `ensureModel` (tests). */
|
|
46
|
+
readonly fetchImpl?: FetchLike;
|
|
47
|
+
/** Injected sidecar host factory (tests). Defaults to a real {@link HostClient}. */
|
|
48
|
+
readonly hostFactory?: () => HostClientLike;
|
|
49
|
+
/** Diagnostic log sink (download progress etc.). Defaults to stderr. */
|
|
50
|
+
readonly log?: (msg: string) => void;
|
|
51
|
+
}
|
|
52
|
+
/** Shut down every live local synthesizer (kills their sidecars). */
|
|
53
|
+
export declare function shutdownLocalTts(): void;
|
|
54
|
+
export declare class LocalPiperSynthesizer implements Synthesizer {
|
|
55
|
+
readonly name = "local-piper";
|
|
56
|
+
readonly mimeType = "audio/wav";
|
|
57
|
+
private readonly voiceId;
|
|
58
|
+
private readonly polishVoiceId;
|
|
59
|
+
private readonly numThreads;
|
|
60
|
+
private readonly modelsDir;
|
|
61
|
+
private readonly provider;
|
|
62
|
+
private readonly sidecarPath;
|
|
63
|
+
private readonly ensureModelImpl;
|
|
64
|
+
private readonly fetchImpl;
|
|
65
|
+
private readonly hostFactory;
|
|
66
|
+
private readonly log;
|
|
67
|
+
private host;
|
|
68
|
+
/** In-flight per-voice download promises — de-dupes concurrent first-use. */
|
|
69
|
+
private readonly ensuring;
|
|
70
|
+
constructor(opts?: LocalPiperOptions);
|
|
71
|
+
synthesize(text: string, opts?: SynthesizeOptions): Promise<SynthesisResult>;
|
|
72
|
+
/** Kill this synthesizer's sidecar (if any) and drop registration. */
|
|
73
|
+
shutdown(): void;
|
|
74
|
+
private getHost;
|
|
75
|
+
/** Resolve on-disk paths for `voice`, downloading + extracting it once. */
|
|
76
|
+
private ensureVoice;
|
|
77
|
+
private voicePaths;
|
|
78
|
+
/** A progress callback that logs a single start line then ~every-10% ticks,
|
|
79
|
+
* and an extraction line — but stays silent when the voice is already
|
|
80
|
+
* present (ensureModel emits only `done`). */
|
|
81
|
+
private makeProgressLogger;
|
|
82
|
+
/** Build a real sidecar-backed host, resolving the platform loader path.
|
|
83
|
+
* Fails clearly when no sherpa binary exists for this platform/arch. */
|
|
84
|
+
private defaultHost;
|
|
85
|
+
}
|
|
86
|
+
export declare function createLocalPiperSynthesizer(opts?: LocalPiperOptions): LocalPiperSynthesizer;
|
|
87
|
+
//# sourceMappingURL=local-tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-tts.d.ts","sourceRoot":"","sources":["../src/local-tts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAc,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EACL,WAAW,EAGX,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAWnE,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,gBAAgB,CAAC;AAO1D;8DAC8D;AAC9D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG3D;AAED,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oCAAoC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;sEAEkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,WAAW,CAAC;IAC9C,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,cAAc,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAMD,qEAAqE;AACrE,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC;AAED,qBAAa,qBAAsB,YAAW,WAAW;IACvD,QAAQ,CAAC,IAAI,iBAAgC;IAC7C,QAAQ,CAAC,QAAQ,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuB;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAwB;IAE5C,OAAO,CAAC,IAAI,CAA+B;IAC3C,6EAA6E;IAC7E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiD;gBAE9D,IAAI,GAAE,iBAAsB;IAmBlC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,eAAe,CAAC;IAmCtF,sEAAsE;IACtE,QAAQ,IAAI,IAAI;IAMhB,OAAO,CAAC,OAAO;IAKf,2EAA2E;YAC7D,WAAW;IAoBzB,OAAO,CAAC,UAAU;IASlB;;mDAE+C;IAC/C,OAAO,CAAC,kBAAkB;IA4B1B;6EACyE;IACzE,OAAO,CAAC,WAAW;CAqBpB;AAED,wBAAgB,2BAA2B,CAAC,IAAI,GAAE,iBAAsB,GAAG,qBAAqB,CAE/F"}
|