Add TTS volume, a browser preview, and the split-audio plan
This commit is contained in:
+41
-1
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user