diff --git a/desktop/src-tauri/src/huddle/playback_speed.rs b/desktop/src-tauri/src/huddle/playback_speed.rs index 07a3433276..e2db2000b6 100644 --- a/desktop/src-tauri/src/huddle/playback_speed.rs +++ b/desktop/src-tauri/src/huddle/playback_speed.rs @@ -245,10 +245,18 @@ mod tests { fn persisted_speed_round_trips_and_rejects_invalid_values() { let directory = tempfile::tempdir().expect("tempdir"); let path = directory.path().join(SETTINGS_FILE); - save_to_path(&path, 1.25).expect("save"); - assert_eq!(load_from_path(&path).expect("load"), 1.25); + save_to_path(&path, playback_speed_dsp::MIN_PLAYBACK_SPEED).expect("save minimum"); + assert_eq!( + load_from_path(&path).expect("load minimum"), + playback_speed_dsp::MIN_PLAYBACK_SPEED + ); + save_to_path(&path, playback_speed_dsp::MAX_PLAYBACK_SPEED).expect("save maximum"); + assert_eq!( + load_from_path(&path).expect("load maximum"), + playback_speed_dsp::MAX_PLAYBACK_SPEED + ); - std::fs::write(&path, br#"{"speed":2.0}"#).expect("invalid fixture"); + std::fs::write(&path, br#"{"speed":4.1}"#).expect("invalid fixture"); assert!(load_from_path(&path).is_err()); } diff --git a/desktop/src-tauri/src/huddle/playback_speed_dsp.rs b/desktop/src-tauri/src/huddle/playback_speed_dsp.rs index fc0f79dbdf..6dfc189780 100644 --- a/desktop/src-tauri/src/huddle/playback_speed_dsp.rs +++ b/desktop/src-tauri/src/huddle/playback_speed_dsp.rs @@ -5,9 +5,9 @@ //! the exact same DSP boundary. /// Slowest supported generated-speech playback speed. -pub const MIN_PLAYBACK_SPEED: f32 = 0.75; +pub const MIN_PLAYBACK_SPEED: f32 = 0.25; /// Fastest supported generated-speech playback speed. -pub const MAX_PLAYBACK_SPEED: f32 = 1.5; +pub const MAX_PLAYBACK_SPEED: f32 = 4.0; /// Default generated-speech playback speed. pub const DEFAULT_PLAYBACK_SPEED: f32 = 1.0; diff --git a/desktop/src/features/settings/ui/SpeechPlaybackSettings.tsx b/desktop/src/features/settings/ui/SpeechPlaybackSettings.tsx index e09f354862..527747d5f9 100644 --- a/desktop/src/features/settings/ui/SpeechPlaybackSettings.tsx +++ b/desktop/src/features/settings/ui/SpeechPlaybackSettings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { getTtsPlaybackSpeed, setTtsPlaybackSpeed, @@ -12,12 +12,15 @@ import { import { SettingsOptionGroup, SettingsOptionRow } from "./SettingsOptionGroup"; const DEFAULT_SPEED = 1; -const MIN_SPEED = 0.75; -const MAX_SPEED = 1.5; +const SLIDER_MIN_SPEED = 0.5; +const SLIDER_MAX_SPEED = 2; +const INPUT_MIN_SPEED = 0.25; +const INPUT_MAX_SPEED = 4; const SPEED_STEP = 0.05; export function SpeechPlaybackSettings() { const [speed, setSpeed] = useState(DEFAULT_SPEED); + const [speedInput, setSpeedInput] = useState(String(DEFAULT_SPEED)); const [error, setError] = useState(null); const [loaded, setLoaded] = useState(false); const active = useRef(true); @@ -26,6 +29,11 @@ export function SpeechPlaybackSettings() { const flushPromise = useRef | null>(null); const hasLocalIntent = useRef(false); + const setDisplayedSpeed = useCallback((nextSpeed: number) => { + setSpeed(nextSpeed); + setSpeedInput(String(nextSpeed)); + }, []); + useEffect(() => { active.current = true; let loadActive = true; @@ -40,7 +48,7 @@ export function SpeechPlaybackSettings() { flushPromise, hasLocalIntent, }, - { setSpeed }, + { setSpeed: setDisplayedSpeed }, savedSpeed, ); } @@ -61,7 +69,7 @@ export function SpeechPlaybackSettings() { hasLocalIntent, }); }; - }, []); + }, [setDisplayedSpeed]); const commitSpeed = (nextSpeed: number) => { commitPlaybackSpeed( @@ -72,11 +80,20 @@ export function SpeechPlaybackSettings() { flushPromise, hasLocalIntent, }, - { persist: setTtsPlaybackSpeed, setError, setSpeed }, + { persist: setTtsPlaybackSpeed, setError, setSpeed: setDisplayedSpeed }, nextSpeed, ); }; + const commitTypedSpeed = () => { + const nextSpeed = Number(speedInput); + if (Number.isFinite(nextSpeed)) { + commitSpeed(nextSpeed); + } else { + setSpeedInput(String(speed)); + } + }; + return ( @@ -89,17 +106,31 @@ export function SpeechPlaybackSettings() { > Speech playback speed -

- Changes generated speech playback without changing voice pitch. -

- - {speed.toFixed(2)}x - +
+ setSpeedInput(event.currentTarget.value)} + onKeyDown={(event) => { + if (event.key === "Enter") { + commitTypedSpeed(); + event.currentTarget.blur(); + } + }} + step={SPEED_STEP} + type="number" + value={speedInput} + /> + +