Add TTS volume, a browser preview, and the split-audio plan

This commit is contained in:
Fisto
2026-09-22 09:27:14 +02:00
parent d99d614c60
commit 420fdca3cf
11 changed files with 287 additions and 73 deletions
+41 -1
View File
@@ -2029,9 +2029,11 @@ async function warmEngine() {
function fillSpeechSliders(tts) {
const speed = typeof tts?.speed === "number" ? tts.speed : 1;
const pitch = typeof tts?.pitch === "number" ? tts.pitch : 0;
const volume = typeof tts?.volume === "number" ? tts.volume : 100;
$("#tts-speed").value = Number.isFinite(speed) ? speed : 1;
$("#tts-pitch").value = Number.isFinite(pitch) ? pitch : 0;
$("#tts-volume").value = Number.isFinite(volume) ? volume : 100;
syncSpeechSliderLabels();
}
@@ -2042,6 +2044,7 @@ function syncSpeechSliderLabels() {
$("#tts-speed-value").textContent = `${speed.toFixed(2)}x`;
$("#tts-pitch-value").textContent = pitchSlider.disabled ? "n/a" : `${pitch > 0 ? "+" : ""}${pitch} st`;
$("#tts-volume-value").textContent = `${Number($("#tts-volume").value)}%`;
const expressionSlider = $("#tts-expression");
if (expressionSlider) {
@@ -2177,6 +2180,7 @@ function effectOptions() {
profile: profileFromControls(),
highpass: Number($("#tts-highpass").value),
echo: Number($("#tts-echo").value),
volume: Number($("#tts-volume").value),
words: $("#tts-words").checked,
wordGapMs: Number($("#tts-word-gap").value),
// VOX and the Half-Life profile are part of the `vox`/`hev` voices now.
@@ -2463,6 +2467,40 @@ async function speakText() {
}
}
async function previewSpeech() {
const text = $("#tts-input").value.trim();
if (!text) return;
const button = $("#tts-preview");
button.disabled = true;
try {
const provider = $("#tts-provider").value;
const speed = Number($("#tts-speed").value);
const pitch = Number($("#tts-pitch").value);
const expressionEl = $("#tts-expression");
const expression =
expressionEl.dataset.dirty === "1" ? Number(expressionEl.value) : state.speech.expression ?? undefined;
const result = await VK.api.post("/api/audio/preview", {
text,
provider,
speed,
pitch,
...(typeof expression === "number" ? { expression } : {}),
...effectOptions()
});
const audio = new Audio(result.url);
audio.volume = Math.min(1, Math.max(0, Number($("#tts-volume").value) / 100));
await audio.play();
} catch (error) {
toast(error.message, "error");
} finally {
button.disabled = false;
}
}
/* Transcript */
function transcriptLine(segment) {
@@ -2880,11 +2918,12 @@ function init() {
});
$("#tts-expression").addEventListener("change", () => void saveSpeech());
buildProfileSliders();
for (const id of ["tts-highpass", "tts-echo", "tts-word-gap"]) {
for (const id of ["tts-highpass", "tts-echo", "tts-word-gap", "tts-volume"]) {
$(`#${id}`).addEventListener("input", () => {
$("#tts-highpass-value").textContent = `${$("#tts-highpass").value}Hz`;
$("#tts-echo-value").textContent = Number($("#tts-echo").value) > 0 ? $("#tts-echo").value : "off";
$("#tts-word-gap-value").textContent = `${$("#tts-word-gap").value}ms`;
$("#tts-volume-value").textContent = `${Number($("#tts-volume").value)}%`;
});
$(`#${id}`).addEventListener("change", () => void saveSpeech());
}
@@ -3065,6 +3104,7 @@ function init() {
$("#tts-close").addEventListener("click", () => {
$("#tts-panel").hidden = true;
});
$("#tts-preview").addEventListener("click", () => void previewSpeech());
$("#tts-speak").addEventListener("click", () => void speakText());
$("#tts-stop").addEventListener("click", async () => {
try {
+6
View File
@@ -170,6 +170,11 @@
<input id="tts-pitch" type="range" min="-12" max="12" step="1" value="0" />
<span id="tts-pitch-value" class="vk-mono vk-dim">0 st</span>
</label>
<label class="vk-tts-slider" title="Playback volume for speech, 100% is the level as synthesized">
<span class="vk-micro-label">volume</span>
<input id="tts-volume" type="range" min="0" max="200" step="5" value="100" />
<span id="tts-volume-value" class="vk-mono vk-dim">100%</span>
</label>
<button id="profile-open" class="vk-chip vk-chip-off" type="button" title="Flextalk voice profile">
voice profile…
</button>
@@ -179,6 +184,7 @@
<div class="vk-tts-meta">
<span class="vk-mono vk-dim" id="tts-status">idle</span>
<button id="tts-stop" class="btn btn-secondary vk-small-btn" type="button">Stop</button>
<button id="tts-preview" class="btn btn-secondary vk-small-btn" type="button" title="Play this line in the browser, not in the call">Preview</button>
<button id="tts-speak" class="btn btn-primary vk-small-btn" type="button">Speak</button>
</div>
</div>