Add double-click resets to the voice settings

This commit is contained in:
Fisto
2026-09-22 19:43:12 +02:00
parent ad06fce7df
commit a20a7de93f
3 changed files with 63 additions and 12 deletions
+10
View File
@@ -40,6 +40,16 @@ mobile draft at 390px.
- [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB
cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`:
`previewed: 2`; thumbs served over `/api/media/<scrape>/<item>/thumb` (200).
- [x] C82 Double-click resets in the voice settings. Every slider in the TTS panel resets on
double-click: the voice-character profile sliders (to the engine default from the voice list),
expression (default, and the override is truly cleared so the label reads `default`), speed
(1), pitch (0), volume (100), highpass (40), echo (off), and word gap (120 ms). Hints say so:
"within 2 snaps to default · double-click a slider to reset it" in Voice character and
"VOX / HEV · double-click resets" in Radio kit, plus a title on the profile sliders. Also
fixed: the speech status and preferences now load at startup (`refreshAudio()`), so the panel
and settings show real values before the first open — the profile defaults were empty until
then, which would have made the profile reset do nothing. Verified in a browser; the UI smoke
test passes.
- [x] C81 remchild discovery and heartbeat-miss notices. The server answers UDP probes on 8798
and beacons `{type,name,port,id}` to every interface broadcast; `remchild` with no `--server`
probes for 5 s, auto-connects on one answer, shows a pick list on several, and falls back to
+51 -10
View File
@@ -2044,15 +2044,19 @@ async function saveSpeech(extra = {}) {
const speed = Number($("#tts-speed").value);
const pitch = Number($("#tts-pitch").value);
const expressionEl = $("#tts-expression");
// A double-click reset clears the override: it must not be resent as a number.
const expression =
expressionEl.dataset.dirty === "1" || typeof state.speech.expression === "number"
? Number(expressionEl.value)
: null;
expressionEl.dataset.reset === "1"
? null
: expressionEl.dataset.dirty === "1" || typeof state.speech.expression === "number"
? Number(expressionEl.value)
: null;
try {
const prefs = await VK.api.post("/api/preferences", {
tts: { provider, language, voice, speed, pitch, expression, ...effectOptions(), ...extra }
});
delete expressionEl.dataset.reset;
state.speech = prefs.tts ?? {
provider: null,
language: null,
@@ -2141,6 +2145,7 @@ function buildProfileSliders() {
const slider = el("input", { type: "range", min: String(min), max: String(max), step: "1" });
slider.id = `tts-profile-${key}`;
slider.dataset.key = key;
slider.title = "Double-click to reset to the engine default";
const value = el("span", { class: "vk-mono vk-dim", text: "default" });
root.append(
@@ -2167,6 +2172,15 @@ function buildProfileSliders() {
}
});
slider.addEventListener("change", () => void saveSpeech());
slider.addEventListener("dblclick", () => {
const engineDefault = state.profileDefaults?.[key];
if (typeof engineDefault !== "number") return;
slider.value = engineDefault;
slider.dataset.dirty = "";
value.textContent = "default";
void saveSpeech();
});
}
}
@@ -2980,6 +2994,11 @@ function init() {
for (const id of ["tts-speed", "tts-pitch"]) {
$(`#${id}`).addEventListener("input", syncSpeechSliderLabels);
$(`#${id}`).addEventListener("change", () => void saveSpeech());
$(`#${id}`).addEventListener("dblclick", () => {
$(`#${id}`).value = id === "tts-speed" ? "1" : "0";
syncSpeechSliderLabels();
void saveSpeech();
});
}
$("#tts-expression").addEventListener("input", () => {
const slider = $("#tts-expression");
@@ -2996,15 +3015,34 @@ function init() {
syncSpeechSliderLabels();
});
$("#tts-expression").addEventListener("change", () => void saveSpeech());
$("#tts-expression").addEventListener("dblclick", () => {
const slider = $("#tts-expression");
const engineDefault = state.expressionDefault;
if (typeof engineDefault !== "number") return;
slider.value = engineDefault;
slider.dataset.dirty = "";
slider.dataset.stored = "0";
slider.dataset.reset = "1";
syncSpeechSliderLabels();
void saveSpeech();
});
buildProfileSliders();
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)}%`;
});
const radioDefaults = { "tts-highpass": 40, "tts-echo": 0, "tts-word-gap": 120, "tts-volume": 100 };
const syncRadioLabels = () => {
$("#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)}%`;
};
for (const id of Object.keys(radioDefaults)) {
$(`#${id}`).addEventListener("input", syncRadioLabels);
$(`#${id}`).addEventListener("change", () => void saveSpeech());
$(`#${id}`).addEventListener("dblclick", () => {
$(`#${id}`).value = String(radioDefaults[id]);
syncRadioLabels();
void saveSpeech();
});
}
$("#tts-words").addEventListener("change", () => void saveSpeech());
$("#tts-vox-clean").addEventListener("change", () => void saveSpeech());
@@ -3246,6 +3284,9 @@ function init() {
renderRightFavourites();
void refreshAll();
// The speech status and preferences feed the TTS panel and the settings
// overlay, so load them at startup instead of on the first panel open.
void refreshAudio();
void refreshHealth();
syncTtsControls();
+2 -2
View File
@@ -568,7 +568,7 @@
<section class="vk-profile-section">
<div class="vk-section-head">
<span class="vk-rail-label vk-rail-label-flat">Voice character</span>
<span class="vk-mono vk-dim">within 2 of the engine default snaps to default</span>
<span class="vk-mono vk-dim">within 2 snaps to default · double-click a slider to reset it</span>
</div>
<div id="tts-profile-sliders" class="vk-profile-grid"></div>
<div class="vk-profile-row">
@@ -593,7 +593,7 @@
<section class="vk-profile-section">
<div class="vk-section-head">
<span class="vk-rail-label vk-rail-label-flat">Radio kit</span>
<span class="vk-mono vk-dim">VOX / HEV</span>
<span class="vk-mono vk-dim">VOX / HEV · double-click resets</span>
</div>
<div class="vk-profile-row">
<span class="vk-micro-label">highpass</span>