add extra recording buffer (#1089)

* add extra recording buffer

* translations
This commit is contained in:
CJ Pais 2026-03-18 21:00:24 +08:00 committed by GitHub
parent 012e066697
commit 5a3e6e33d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 142 additions and 3 deletions

View file

@ -335,6 +335,7 @@ pub fn run(cli_args: CliArgs) {
shortcut::change_overlay_position_setting,
shortcut::change_debug_mode_setting,
shortcut::change_word_correction_threshold_setting,
shortcut::change_extra_recording_buffer_setting,
shortcut::change_paste_method_setting,
shortcut::get_available_typing_tools,
shortcut::change_typing_tool_setting,

View file

@ -4,7 +4,7 @@ use crate::settings::{get_settings, AppSettings};
use crate::utils;
use log::{debug, error, info};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::time::{Duration, Instant};
use tauri::Manager;
fn set_mute(mute: bool) {
@ -380,6 +380,16 @@ impl AudioRecordingManager {
*state = RecordingState::Idle;
drop(state);
// Optionally keep recording for a bit longer to capture trailing audio
let settings = get_settings(&self.app_handle);
if settings.extra_recording_buffer_ms > 0 {
debug!(
"Extra recording buffer: sleeping {}ms before stopping",
settings.extra_recording_buffer_ms
);
std::thread::sleep(Duration::from_millis(settings.extra_recording_buffer_ms));
}
let samples = if let Some(rec) = self.recorder.lock().unwrap().as_ref() {
match rec.stop() {
Ok(buf) => buf,

View file

@ -395,6 +395,8 @@ pub struct AppSettings {
pub whisper_accelerator: WhisperAcceleratorSetting,
#[serde(default)]
pub ort_accelerator: OrtAcceleratorSetting,
#[serde(default)]
pub extra_recording_buffer_ms: u64,
}
fn default_model() -> String {
@ -762,6 +764,7 @@ pub fn get_default_settings() -> AppSettings {
custom_filler_words: None,
whisper_accelerator: WhisperAcceleratorSetting::default(),
ort_accelerator: OrtAcceleratorSetting::default(),
extra_recording_buffer_ms: 0,
}
}

View file

@ -660,6 +660,15 @@ pub fn change_word_correction_threshold_setting(
Ok(())
}
#[tauri::command]
#[specta::specta]
pub fn change_extra_recording_buffer_setting(app: AppHandle, ms: u64) -> Result<(), String> {
let mut settings = settings::get_settings(&app);
settings.extra_recording_buffer_ms = ms;
settings::write_settings(&app, settings);
Ok(())
}
#[tauri::command]
#[specta::specta]
pub fn change_paste_method_setting(app: AppHandle, method: String) -> Result<(), String> {

View file

@ -109,6 +109,14 @@ async changeWordCorrectionThresholdSetting(threshold: number) : Promise<Result<n
else return { status: "error", error: e as any };
}
},
async changeExtraRecordingBufferSetting(ms: number) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_extra_recording_buffer_setting", { ms }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
}
},
async changePasteMethodSetting(method: string) : Promise<Result<null, string>> {
try {
return { status: "ok", data: await TAURI_INVOKE("change_paste_method_setting", { method }) };
@ -779,7 +787,7 @@ async isLaptop() : Promise<Result<boolean, string>> {
/** user-defined types **/
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; auto_submit?: boolean; auto_submit_key?: AutoSubmitKey; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation; show_tray_icon?: boolean; paste_delay_ms?: number; typing_tool?: TypingTool; external_script_path: string | null; custom_filler_words?: string[] | null; whisper_accelerator?: WhisperAcceleratorSetting; ort_accelerator?: OrtAcceleratorSetting }
export type AppSettings = { bindings: Partial<{ [key in string]: ShortcutBinding }>; push_to_talk: boolean; audio_feedback: boolean; audio_feedback_volume?: number; sound_theme?: SoundTheme; start_hidden?: boolean; autostart_enabled?: boolean; update_checks_enabled?: boolean; selected_model?: string; always_on_microphone?: boolean; selected_microphone?: string | null; clamshell_microphone?: string | null; selected_output_device?: string | null; translate_to_english?: boolean; selected_language?: string; overlay_position?: OverlayPosition; debug_mode?: boolean; log_level?: LogLevel; custom_words?: string[]; model_unload_timeout?: ModelUnloadTimeout; word_correction_threshold?: number; history_limit?: number; recording_retention_period?: RecordingRetentionPeriod; paste_method?: PasteMethod; clipboard_handling?: ClipboardHandling; auto_submit?: boolean; auto_submit_key?: AutoSubmitKey; post_process_enabled?: boolean; post_process_provider_id?: string; post_process_providers?: PostProcessProvider[]; post_process_api_keys?: Partial<{ [key in string]: string }>; post_process_models?: Partial<{ [key in string]: string }>; post_process_prompts?: LLMPrompt[]; post_process_selected_prompt_id?: string | null; mute_while_recording?: boolean; append_trailing_space?: boolean; app_language?: string; experimental_enabled?: boolean; keyboard_implementation?: KeyboardImplementation; show_tray_icon?: boolean; paste_delay_ms?: number; typing_tool?: TypingTool; external_script_path: string | null; custom_filler_words?: string[] | null; whisper_accelerator?: WhisperAcceleratorSetting; ort_accelerator?: OrtAcceleratorSetting; extra_recording_buffer_ms?: number }
export type AudioDevice = { index: string; name: string; is_default: boolean }
export type AutoSubmitKey = "enter" | "ctrl_enter" | "cmd_enter"
export type AvailableAccelerators = { whisper: string[]; ort: string[] }

View file

@ -4,6 +4,7 @@ import { type } from "@tauri-apps/plugin-os";
import { WordCorrectionThreshold } from "./WordCorrectionThreshold";
import { LogLevelSelector } from "./LogLevelSelector";
import { PasteDelay } from "./PasteDelay";
import { RecordingBuffer } from "./RecordingBuffer";
import { SettingsGroup } from "../../ui/SettingsGroup";
import { AlwaysOnMicrophone } from "../AlwaysOnMicrophone";
import { SoundPicker } from "../SoundPicker";
@ -29,6 +30,7 @@ export const DebugSettings: React.FC = () => {
/>
<WordCorrectionThreshold descriptionMode="tooltip" grouped={true} />
<PasteDelay descriptionMode="tooltip" grouped={true} />
<RecordingBuffer descriptionMode="tooltip" grouped={true} />
<AlwaysOnMicrophone descriptionMode="tooltip" grouped={true} />
<ClamshellMicrophoneSelector descriptionMode="tooltip" grouped={true} />
{/* Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration */}

View file

@ -0,0 +1,36 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Slider } from "../../ui/Slider";
import { useSettings } from "../../../hooks/useSettings";
interface RecordingBufferProps {
descriptionMode?: "tooltip" | "inline";
grouped?: boolean;
}
export const RecordingBuffer: React.FC<RecordingBufferProps> = ({
descriptionMode = "tooltip",
grouped = false,
}) => {
const { t } = useTranslation();
const { settings, updateSetting } = useSettings();
const handleBufferChange = (value: number) => {
updateSetting("extra_recording_buffer_ms", value);
};
return (
<Slider
value={settings?.extra_recording_buffer_ms ?? 0}
onChange={handleBufferChange}
min={0}
max={1500}
step={50}
label={t("settings.debug.recordingBuffer.title")}
description={t("settings.debug.recordingBuffer.description")}
descriptionMode={descriptionMode}
grouped={grouped}
formatValue={(v) => `${v}ms`}
/>
);
};

View file

@ -63,7 +63,7 @@ export const Slider: React.FC<SliderProps> = ({
}}
/>
{showValue && (
<span className="text-sm font-medium text-text/90 min-w-10 text-end">
<span className="text-sm font-medium text-text/90 w-12 text-end">
{formatValue(value)}
</span>
)}

View file

@ -462,6 +462,10 @@
"pasteDelay": {
"title": "تأخير اللصق",
"description": "التأخير قبل إرسال ضغطة مفتاح اللصق (بالمللي ثانية). قم بزيادتها إذا تم لصق نص خاطئ."
},
"recordingBuffer": {
"title": "مخزن التسجيل الإضافي",
"description": "وقت إضافي (بالمللي ثانية) للاستمرار في التسجيل بعد تحرير المفتاح، لالتقاط الصوت المتبقي. 0 = لا مخزن إضافي."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Zpoždění vložení",
"description": "Zpoždění před odesláním klávesy pro vložení (v milisekundách). Zvyšte, pokud se vkládá špatný text."
},
"recordingBuffer": {
"title": "Extra vyrovnávací paměť nahrávání",
"description": "Extra čas (v milisekundách) pro pokračování nahrávání po uvolnění klávesy, pro zachycení zbývajícího zvuku. 0 = žádná extra vyrovnávací paměť."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Einfügeverzögerung",
"description": "Verzögerung vor dem Senden des Einfüge-Tastendrucks (in Millisekunden). Erhöhen Sie den Wert, wenn falscher Text eingefügt wird."
},
"recordingBuffer": {
"title": "Zusätzlicher Aufnahmepuffer",
"description": "Zusätzliche Zeit (in Millisekunden), um nach dem Loslassen der Taste weiterzuzeichnen, um nachlaufendes Audio aufzunehmen. 0 = kein zusätzlicher Puffer."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Paste Delay",
"description": "Delay before sending paste keystroke (in milliseconds). Increase if wrong text is being pasted."
},
"recordingBuffer": {
"title": "Extra Recording Buffer",
"description": "Extra time (in milliseconds) to keep recording after you release the key, to capture trailing audio. 0 = no extra buffer."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Retraso de pegado",
"description": "Retraso antes de enviar la pulsación de tecla de pegar (en milisegundos). Aumente si se está pegando texto incorrecto."
},
"recordingBuffer": {
"title": "Búfer de grabación adicional",
"description": "Tiempo adicional (en milisegundos) para seguir grabando después de soltar la tecla, para capturar el audio restante. 0 = sin búfer adicional."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Délai de collage",
"description": "Délai avant l'envoi de la touche de collage (en millisecondes). Augmentez si le mauvais texte est collé."
},
"recordingBuffer": {
"title": "Tampon d'enregistrement supplémentaire",
"description": "Temps supplémentaire (en millisecondes) pour continuer l'enregistrement après avoir relâché la touche, pour capturer l'audio restant. 0 = pas de tampon supplémentaire."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Ritardo incolla",
"description": "Ritardo prima dell'invio del tasto incolla (in millisecondi). Aumentare se viene incollato il testo sbagliato."
},
"recordingBuffer": {
"title": "Buffer di registrazione extra",
"description": "Tempo extra (in millisecondi) per continuare a registrare dopo aver rilasciato il tasto, per catturare l'audio finale. 0 = nessun buffer extra."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "貼り付け遅延",
"description": "貼り付けキー送信前の遅延(ミリ秒)。間違ったテキストが貼り付けられる場合は増やしてください。"
},
"recordingBuffer": {
"title": "追加録音バッファ",
"description": "キーを離した後に録音を続ける追加時間ミリ秒。末尾の音声を捕捉するため。0 = 追加バッファなし。"
}
},
"about": {

View file

@ -480,6 +480,10 @@
"title": "붙여넣기 지연",
"description": "붙여넣기 키 입력을 보내기 전 지연 시간(밀리초). 잘못된 텍스트가 붙여넣어지면 늘리세요."
},
"recordingBuffer": {
"title": "추가 녹음 버퍼",
"description": "키를 놓은 후 추가로 녹음을 계속하는 시간(밀리초). 후행 오디오를 캡처하기 위함. 0 = 추가 버퍼 없음."
},
"paths": {
"appData": "앱 데이터:",
"models": "모델:",

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Opóźnienie wklejania",
"description": "Opóźnienie przed wysłaniem klawisza wklejania (w milisekundach). Zwiększ, jeśli wklejany jest nieprawidłowy tekst."
},
"recordingBuffer": {
"title": "Dodatkowy bufor nagrywania",
"description": "Dodatkowy czas (w milisekundach) na kontynuowanie nagrywania po zwolnieniu klawisza, aby przechwycić końcowy dźwięk. 0 = brak dodatkowego bufora."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Atraso de colagem",
"description": "Atraso antes de enviar a tecla de colar (em milissegundos). Aumente se o texto errado estiver sendo colado."
},
"recordingBuffer": {
"title": "Buffer de gravação extra",
"description": "Tempo extra (em milissegundos) para continuar gravando após soltar a tecla, para capturar áudio restante. 0 = sem buffer extra."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Задержка вставки",
"description": "Задержка перед отправкой нажатия клавиши вставки (в миллисекундах). Увеличьте, если вставляется неправильный текст."
},
"recordingBuffer": {
"title": "Дополнительный буфер записи",
"description": "Дополнительное время (в миллисекундах) для продолжения записи после отпускания клавиши, чтобы захватить завершающий звук. 0 = без дополнительного буфера."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Yapıştırma gecikmesi",
"description": "Yapıştırma tuşu göndermeden önce gecikme (milisaniye cinsinden). Yanlış metin yapıştırılıyorsa artırın."
},
"recordingBuffer": {
"title": "Ekstra kayıt tamponu",
"description": "Tuşu bıraktıktan sonra arka plandaki sesi yakalamak için kaydı sürdürme süresi (milisaniye). 0 = ekstra tampon yok."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Затримка вставки",
"description": "Затримка перед надсиланням натискання клавіші вставки (у мілісекундах). Збільшіть, якщо вставляється неправильний текст."
},
"recordingBuffer": {
"title": "Додатковий буфер запису",
"description": "Додатковий час (у мілісекундах) для продовження запису після відпускання клавіші, щоб захопити завершальний звук. 0 = без додаткового буфера."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "Độ trễ dán",
"description": "Độ trễ trước khi gửi phím dán (tính bằng mili giây). Tăng nếu văn bản sai đang được dán."
},
"recordingBuffer": {
"title": "Bộ đệm ghi âm thêm",
"description": "Thời gian thêm (tính bằng mili giây) để tiếp tục ghi âm sau khi nhả phím, để thu âm thanh cuối. 0 = không có bộ đệm thêm."
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "貼上延遲",
"description": "發送貼上按鍵前的延遲(毫秒)。如果貼上了錯誤的文字,請增加此值"
},
"recordingBuffer": {
"title": "額外錄音緩衝",
"description": "放開按鍵後繼續錄音的額外時間毫秒以捕捉尾音。0 = 無額外緩衝。"
}
},
"about": {

View file

@ -484,6 +484,10 @@
"pasteDelay": {
"title": "粘贴延迟",
"description": "发送粘贴按键前的延迟(毫秒)。如果粘贴了错误的文本,请增加此值。"
},
"recordingBuffer": {
"title": "额外录音缓冲",
"description": "放开按键后继续录音的额外时间毫秒以捕捉尾音。0 = 无额外缓冲。"
}
},
"about": {

View file

@ -147,6 +147,8 @@ const settingUpdaters: {
),
ort_accelerator: (value) =>
commands.changeOrtAcceleratorSetting(value as OrtAcceleratorSetting),
extra_recording_buffer_ms: (value) =>
commands.changeExtraRecordingBufferSetting(value as number),
};
export const useSettingsStore = create<SettingsStore>()(