Add the VOX prosody preference and panel controls
This commit is contained in:
@@ -561,10 +561,26 @@ function buildServer(): McpServer {
|
||||
highpass: z.number().min(40).max(400).optional().describe("High-pass filter Hz"),
|
||||
echo: z.number().min(0).max(100).optional().describe("Mono echo amount"),
|
||||
words: z.boolean().optional().describe("Synthesize each word and merge with gaps"),
|
||||
wordGapMs: z.number().min(0).max(500).optional().describe("Silence between words in ms")
|
||||
wordGapMs: z.number().min(0).max(500).optional().describe("Silence between words in ms"),
|
||||
vox: z.boolean().optional().describe("NetTTS-style VOX prosody (Flextalk voices)"),
|
||||
voxClean: z.boolean().optional().describe("VOX without the staccato wrapper")
|
||||
}
|
||||
},
|
||||
async ({ text, provider, voice, speed, pitch, expression, profile, highpass, echo, words, wordGapMs }) =>
|
||||
async ({
|
||||
text,
|
||||
provider,
|
||||
voice,
|
||||
speed,
|
||||
pitch,
|
||||
expression,
|
||||
profile,
|
||||
highpass,
|
||||
echo,
|
||||
words,
|
||||
wordGapMs,
|
||||
vox,
|
||||
voxClean
|
||||
}) =>
|
||||
jsonText(
|
||||
await audio.speak(text, {
|
||||
provider,
|
||||
@@ -576,7 +592,9 @@ function buildServer(): McpServer {
|
||||
highpass,
|
||||
echo,
|
||||
words,
|
||||
wordGapMs
|
||||
wordGapMs,
|
||||
vox,
|
||||
voxClean
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
+13
-3
@@ -30,6 +30,10 @@ export interface TtsPreferences {
|
||||
words: boolean;
|
||||
/** Silence between words in milliseconds, 0-500. */
|
||||
wordGapMs: number | null;
|
||||
/** NetTTS-style VOX prosody (Flextalk voices only). */
|
||||
vox: boolean;
|
||||
/** VOX without the staccato wrapper. */
|
||||
voxClean: boolean;
|
||||
}
|
||||
|
||||
const PROFILE_RANGES: Record<string, [number, number]> = {
|
||||
@@ -146,7 +150,9 @@ const DEFAULTS: WebPreferences = {
|
||||
highpass: null,
|
||||
echo: null,
|
||||
words: false,
|
||||
wordGapMs: null
|
||||
wordGapMs: null,
|
||||
vox: false,
|
||||
voxClean: false
|
||||
},
|
||||
audio: { duck: true },
|
||||
favourites: { users: [], channels: [] },
|
||||
@@ -340,7 +346,9 @@ export class PreferencesStore {
|
||||
highpass: normalizeRange(patch.tts?.highpass, 40, 400),
|
||||
echo: normalizeRange(patch.tts?.echo, 0, 100),
|
||||
words: patch.tts?.words === true,
|
||||
wordGapMs: normalizeRange(patch.tts?.wordGapMs, 0, 500)
|
||||
wordGapMs: normalizeRange(patch.tts?.wordGapMs, 0, 500),
|
||||
vox: patch.tts?.vox === true,
|
||||
voxClean: patch.tts?.voxClean === true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -405,7 +413,9 @@ export class PreferencesStore {
|
||||
highpass: normalizeRange(raw?.tts?.highpass, 40, 400),
|
||||
echo: normalizeRange(raw?.tts?.echo, 0, 100),
|
||||
words: raw?.tts?.words === true,
|
||||
wordGapMs: normalizeRange(raw?.tts?.wordGapMs, 0, 500)
|
||||
wordGapMs: normalizeRange(raw?.tts?.wordGapMs, 0, 500),
|
||||
vox: raw?.tts?.vox === true,
|
||||
voxClean: raw?.tts?.voxClean === true
|
||||
},
|
||||
audio: {
|
||||
duck: raw?.audio?.duck !== false
|
||||
|
||||
+9
-1
@@ -34,6 +34,10 @@ export interface TtsOptions {
|
||||
words?: boolean;
|
||||
/** Silence between words in milliseconds, 0 to 500. */
|
||||
wordGapMs?: number;
|
||||
/** NetTTS-style VOX prosody (Flextalk voices only). */
|
||||
vox?: boolean;
|
||||
/** VOX without the staccato wrapper. */
|
||||
voxClean?: boolean;
|
||||
}
|
||||
|
||||
export interface TtsEngine {
|
||||
@@ -348,6 +352,8 @@ export class Tts {
|
||||
.replace("{echo}", options.echo !== undefined ? String(options.echo) : "")
|
||||
.replace("{words}", options.words ? "1" : "")
|
||||
.replace("{word_gap_ms}", options.wordGapMs !== undefined ? String(options.wordGapMs) : "")
|
||||
.replace("{vox}", options.vox ? "1" : "")
|
||||
.replace("{vox_clean}", options.voxClean ? "1" : "")
|
||||
)
|
||||
: ["espeak-ng", ...(options.voice ? ["-v", options.voice] : []), "-w", out, "-f", textFile];
|
||||
|
||||
@@ -371,7 +377,9 @@ export class Tts {
|
||||
...(options.words ? { VESKSLOP_TTS_WORDS: "1" } : {}),
|
||||
...(options.wordGapMs !== undefined
|
||||
? { VESKSLOP_TTS_WORD_GAP_MS: String(options.wordGapMs) }
|
||||
: {})
|
||||
: {}),
|
||||
...(options.vox ? { VESKSLOP_TTS_VOX: "1" } : {}),
|
||||
...(options.voxClean ? { VESKSLOP_TTS_VOX_CLEAN: "1" } : {})
|
||||
},
|
||||
stdio: ["ignore", "ignore", "pipe"]
|
||||
});
|
||||
|
||||
+8
-2
@@ -508,7 +508,9 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
highpass: tts?.highpass ?? null,
|
||||
echo: tts?.echo ?? null,
|
||||
words: tts?.words === true,
|
||||
wordGapMs: tts?.wordGapMs ?? null
|
||||
wordGapMs: tts?.wordGapMs ?? null,
|
||||
vox: tts?.vox === true,
|
||||
voxClean: tts?.voxClean === true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -756,6 +758,8 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
const words = req.body?.words !== undefined ? req.body.words === true : prefs.tts.words;
|
||||
const wordGapMs =
|
||||
req.body?.wordGapMs !== undefined ? Number(req.body.wordGapMs) : prefs.tts.wordGapMs ?? undefined;
|
||||
const vox = req.body?.vox !== undefined ? req.body.vox === true : prefs.tts.vox;
|
||||
const voxClean = req.body?.voxClean !== undefined ? req.body.voxClean === true : prefs.tts.voxClean;
|
||||
|
||||
const result = await deps.audio.speak(text, {
|
||||
provider: provider ?? undefined,
|
||||
@@ -767,7 +771,9 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
...(Number.isFinite(highpass) ? { highpass } : {}),
|
||||
...(Number.isFinite(echo) ? { echo } : {}),
|
||||
...(words ? { words: true } : {}),
|
||||
...(Number.isFinite(wordGapMs) ? { wordGapMs } : {})
|
||||
...(Number.isFinite(wordGapMs) ? { wordGapMs } : {}),
|
||||
...(vox ? { vox: true } : {}),
|
||||
...(voxClean ? { voxClean: true } : {})
|
||||
});
|
||||
deps.getPuppet()?.appendAction({
|
||||
type: "audio",
|
||||
|
||||
Reference in New Issue
Block a user