mirror of
https://github.com/block/buzz.git
synced 2026-11-01 06:51:17 +00:00
Expand speech playback speed controls
Signed-off-by: John Tennant <jtennant@squareup.com>
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<string | null>(null);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const active = useRef(true);
|
||||
@@ -26,6 +29,11 @@ export function SpeechPlaybackSettings() {
|
||||
const flushPromise = useRef<Promise<void> | 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 (
|
||||
<SettingsOptionGroup>
|
||||
<SettingsOptionRow className="items-start">
|
||||
@@ -89,17 +106,31 @@ export function SpeechPlaybackSettings() {
|
||||
>
|
||||
Speech playback speed
|
||||
</label>
|
||||
<p className="text-sm font-normal text-muted-foreground">
|
||||
Changes generated speech playback without changing voice pitch.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<span
|
||||
aria-live="polite"
|
||||
className="min-w-10 text-right text-sm tabular-nums"
|
||||
>
|
||||
{speed.toFixed(2)}x
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<input
|
||||
aria-label="Speech playback speed value"
|
||||
className="h-8 w-16 rounded-md border border-input bg-background px-2 text-right text-sm tabular-nums"
|
||||
disabled={!loaded}
|
||||
max={INPUT_MAX_SPEED}
|
||||
min={INPUT_MIN_SPEED}
|
||||
onBlur={commitTypedSpeed}
|
||||
onChange={(event) => setSpeedInput(event.currentTarget.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
commitTypedSpeed();
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
step={SPEED_STEP}
|
||||
type="number"
|
||||
value={speedInput}
|
||||
/>
|
||||
<span aria-hidden="true" className="text-sm">
|
||||
×
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
disabled={!loaded || speed === DEFAULT_SPEED}
|
||||
onClick={() => commitSpeed(DEFAULT_SPEED)}
|
||||
@@ -116,10 +147,12 @@ export function SpeechPlaybackSettings() {
|
||||
className="mt-3 w-full accent-primary"
|
||||
disabled={!loaded}
|
||||
id="speech-playback-speed"
|
||||
max={MAX_SPEED}
|
||||
min={MIN_SPEED}
|
||||
max={SLIDER_MAX_SPEED}
|
||||
min={SLIDER_MIN_SPEED}
|
||||
onBlur={(event) => commitSpeed(Number(event.currentTarget.value))}
|
||||
onChange={(event) => setSpeed(Number(event.currentTarget.value))}
|
||||
onChange={(event) =>
|
||||
setDisplayedSpeed(Number(event.currentTarget.value))
|
||||
}
|
||||
onKeyUp={(event) => commitSpeed(Number(event.currentTarget.value))}
|
||||
onPointerUp={(event) =>
|
||||
commitSpeed(Number(event.currentTarget.value))
|
||||
|
||||
Reference in New Issue
Block a user