From a20a7de93fc7a8d469ea24ac25861115f1134a3a Mon Sep 17 00:00:00 2001 From: Fisto Date: Tue, 22 Sep 2026 19:43:12 +0200 Subject: [PATCH] Add double-click resets to the voice settings --- CURRENT.md | 10 +++++++ poc/public/app.js | 61 ++++++++++++++++++++++++++++++++++++------- poc/public/index.html | 4 +-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/CURRENT.md b/CURRENT.md index a6fc16e..2916924 100644 --- a/CURRENT.md +++ b/CURRENT.md @@ -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///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 diff --git a/poc/public/app.js b/poc/public/app.js index 6d05621..8bc0a81 100644 --- a/poc/public/app.js +++ b/poc/public/app.js @@ -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(); diff --git a/poc/public/index.html b/poc/public/index.html index b256f1e..e0c26bd 100644 --- a/poc/public/index.html +++ b/poc/public/index.html @@ -568,7 +568,7 @@
Voice character - within 2 of the engine default snaps to default + within 2 snaps to default · double-click a slider to reset it
@@ -593,7 +593,7 @@
Radio kit - VOX / HEV + VOX / HEV · double-click resets
highpass