Add MicroslopSam TTS, Flextalk voice profiles, effects, and engine controls
This commit is contained in:
+103
-8
@@ -25,7 +25,7 @@ import type { PreferencesStore, WebPreferences } from "./preferences.js";
|
||||
import type { Puppet } from "./puppet.js";
|
||||
import { ensureTls } from "./tls.js";
|
||||
import type { TranscriptStore } from "./transcript.js";
|
||||
import type { TtsVoice } from "./tts.js";
|
||||
import type { TtsProvider, TtsVoice } from "./tts.js";
|
||||
|
||||
const PINNED_LANGUAGES = ["en", "sv", "de", "da", "nb", "no", "ru", "chr"];
|
||||
const LANGUAGE_NAMES: Record<string, string> = {
|
||||
@@ -401,7 +401,7 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
app.post(
|
||||
"/api/preferences",
|
||||
wrap(async (req, res) => {
|
||||
const { visibleGuilds, tts, favourites } = req.body ?? {};
|
||||
const { visibleGuilds, tts, audio, favourites } = req.body ?? {};
|
||||
|
||||
if (visibleGuilds !== undefined && visibleGuilds !== null && !Array.isArray(visibleGuilds)) {
|
||||
throw new HttpError(400, "visibleGuilds must be an array of guild IDs or null");
|
||||
@@ -414,7 +414,18 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
patch.tts = {
|
||||
provider: tts?.provider ?? null,
|
||||
language: tts?.language ?? null,
|
||||
voice: tts?.voice ?? null
|
||||
voice: tts?.voice ?? null,
|
||||
speed: tts?.speed ?? null,
|
||||
pitch: tts?.pitch ?? null,
|
||||
expression: tts?.expression ?? null,
|
||||
profile: tts?.profile ?? {},
|
||||
profilePresets: Array.isArray(tts?.profilePresets)
|
||||
? tts.profilePresets.slice(0, 5)
|
||||
: deps.preferences.get().tts.profilePresets,
|
||||
highpass: tts?.highpass ?? null,
|
||||
echo: tts?.echo ?? null,
|
||||
words: tts?.words === true,
|
||||
wordGapMs: tts?.wordGapMs ?? null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -425,6 +436,11 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
};
|
||||
}
|
||||
|
||||
if (audio !== undefined) {
|
||||
patch.audio = { duck: audio?.duck !== false };
|
||||
deps.audio.setDuckEnabled(patch.audio.duck);
|
||||
}
|
||||
|
||||
res.json({ ok: true, result: deps.preferences.update(patch) });
|
||||
})
|
||||
);
|
||||
@@ -486,6 +502,41 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/api/voice/volume",
|
||||
wrap(async (req, res) => {
|
||||
let userIds = req.query.userIds
|
||||
? String(req.query.userIds)
|
||||
.split(",")
|
||||
.map(id => id.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (!userIds.length) {
|
||||
const voice = (await requireCore().voiceGet()) as { members?: Array<{ id: string }> } | undefined;
|
||||
userIds = (voice?.members ?? []).map(member => member.id);
|
||||
}
|
||||
|
||||
if (!userIds.length) {
|
||||
res.json({ ok: true, result: { outputVolume: null, users: [] } });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({ ok: true, result: await requireCore().userVolume(userIds) });
|
||||
})
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/voice/volume",
|
||||
wrap(async (req, res) => {
|
||||
const userId = String(req.body?.userId ?? "");
|
||||
const volume = Number(req.body?.volume);
|
||||
const result = await pacedVoice(() => requireCore().userSetVolume(userId, volume));
|
||||
deps.getPuppet()?.appendAction({ type: "voice", action: "set-volume", userId, volume });
|
||||
res.json({ ok: true, result });
|
||||
})
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/voice/join",
|
||||
wrap(async (req, res) => {
|
||||
@@ -526,8 +577,28 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
|
||||
app.get(
|
||||
"/api/audio/tts/voices",
|
||||
wrap(async (_req, res) => {
|
||||
res.json({ ok: true, result: groupVoices(await deps.audio.ttsVoices()) });
|
||||
wrap(async (req, res) => {
|
||||
const engine = req.query.engine ? String(req.query.engine) : undefined;
|
||||
res.json({ ok: true, result: groupVoices(await deps.audio.ttsVoices(engine as TtsProvider | undefined)) });
|
||||
})
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/audio/tts/warm",
|
||||
wrap(async (req, res) => {
|
||||
const requested = req.body?.engine;
|
||||
const engine = ["local", "espeak", "command", "api"].includes(requested)
|
||||
? (requested as TtsProvider)
|
||||
: undefined;
|
||||
|
||||
const result = await deps.audio.warmTts(engine);
|
||||
deps.getPuppet()?.appendAction({
|
||||
type: "audio",
|
||||
action: "warm-engine",
|
||||
engine: result.engine,
|
||||
ms: result.ms
|
||||
});
|
||||
res.json({ ok: true, result });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -568,11 +639,35 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
const text = String(req.body?.text ?? "");
|
||||
const prefs = deps.preferences.get();
|
||||
|
||||
const provider =
|
||||
req.body?.provider === "api" || req.body?.provider === "local" ? req.body.provider : prefs.tts.provider;
|
||||
const requested = req.body?.provider;
|
||||
const provider = ["api", "local", "espeak", "command"].includes(requested)
|
||||
? requested
|
||||
: prefs.tts.provider;
|
||||
const voice = req.body?.voice ? String(req.body.voice) : prefs.tts.voice ?? undefined;
|
||||
const speed = req.body?.speed !== undefined ? Number(req.body.speed) : prefs.tts.speed ?? undefined;
|
||||
const pitch = req.body?.pitch !== undefined ? Number(req.body.pitch) : prefs.tts.pitch ?? undefined;
|
||||
const expression =
|
||||
req.body?.expression !== undefined ? Number(req.body.expression) : prefs.tts.expression ?? undefined;
|
||||
const profile = req.body?.profile && typeof req.body.profile === "object" ? req.body.profile : prefs.tts.profile;
|
||||
const highpass =
|
||||
req.body?.highpass !== undefined ? Number(req.body.highpass) : prefs.tts.highpass ?? undefined;
|
||||
const echo = req.body?.echo !== undefined ? Number(req.body.echo) : prefs.tts.echo ?? undefined;
|
||||
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 result = await deps.audio.speak(text, { provider: provider ?? undefined, voice });
|
||||
const result = await deps.audio.speak(text, {
|
||||
provider: provider ?? undefined,
|
||||
voice,
|
||||
...(Number.isFinite(speed) ? { speed } : {}),
|
||||
...(Number.isFinite(pitch) ? { pitch } : {}),
|
||||
...(Number.isFinite(expression) ? { expression } : {}),
|
||||
...(profile && Object.keys(profile).length ? { profile } : {}),
|
||||
...(Number.isFinite(highpass) ? { highpass } : {}),
|
||||
...(Number.isFinite(echo) ? { echo } : {}),
|
||||
...(words ? { words: true } : {}),
|
||||
...(Number.isFinite(wordGapMs) ? { wordGapMs } : {})
|
||||
});
|
||||
deps.getPuppet()?.appendAction({
|
||||
type: "audio",
|
||||
action: "speak",
|
||||
|
||||
Reference in New Issue
Block a user