From 5a3e6e33d1ffb70dbc3cb8a33e8e92c947646e0b Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Wed, 18 Mar 2026 21:00:24 +0800 Subject: [PATCH] add extra recording buffer (#1089) * add extra recording buffer * translations --- src-tauri/src/lib.rs | 1 + src-tauri/src/managers/audio.rs | 12 ++++++- src-tauri/src/settings.rs | 3 ++ src-tauri/src/shortcut/mod.rs | 9 +++++ src/bindings.ts | 10 +++++- .../settings/debug/DebugSettings.tsx | 2 ++ .../settings/debug/RecordingBuffer.tsx | 36 +++++++++++++++++++ src/components/ui/Slider.tsx | 2 +- src/i18n/locales/ar/translation.json | 4 +++ src/i18n/locales/cs/translation.json | 4 +++ src/i18n/locales/de/translation.json | 4 +++ src/i18n/locales/en/translation.json | 4 +++ src/i18n/locales/es/translation.json | 4 +++ src/i18n/locales/fr/translation.json | 4 +++ src/i18n/locales/it/translation.json | 4 +++ src/i18n/locales/ja/translation.json | 4 +++ src/i18n/locales/ko/translation.json | 4 +++ src/i18n/locales/pl/translation.json | 4 +++ src/i18n/locales/pt/translation.json | 4 +++ src/i18n/locales/ru/translation.json | 4 +++ src/i18n/locales/tr/translation.json | 4 +++ src/i18n/locales/uk/translation.json | 4 +++ src/i18n/locales/vi/translation.json | 4 +++ src/i18n/locales/zh-TW/translation.json | 4 +++ src/i18n/locales/zh/translation.json | 4 +++ src/stores/settingsStore.ts | 2 ++ 26 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/components/settings/debug/RecordingBuffer.tsx diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d02df67..34ec8ce 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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, diff --git a/src-tauri/src/managers/audio.rs b/src-tauri/src/managers/audio.rs index 0fd3f0e..b887c3e 100644 --- a/src-tauri/src/managers/audio.rs +++ b/src-tauri/src/managers/audio.rs @@ -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, diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index a713dae..d7beb8c 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -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, } } diff --git a/src-tauri/src/shortcut/mod.rs b/src-tauri/src/shortcut/mod.rs index 5e95d46..c6e96bf 100644 --- a/src-tauri/src/shortcut/mod.rs +++ b/src-tauri/src/shortcut/mod.rs @@ -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> { diff --git a/src/bindings.ts b/src/bindings.ts index 35abc27..8dd24cd 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -109,6 +109,14 @@ async changeWordCorrectionThresholdSetting(threshold: number) : Promise> { + 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> { try { return { status: "ok", data: await TAURI_INVOKE("change_paste_method_setting", { method }) }; @@ -779,7 +787,7 @@ async isLaptop() : Promise> { /** 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[] } diff --git a/src/components/settings/debug/DebugSettings.tsx b/src/components/settings/debug/DebugSettings.tsx index 02ad527..49bd9ec 100644 --- a/src/components/settings/debug/DebugSettings.tsx +++ b/src/components/settings/debug/DebugSettings.tsx @@ -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 = () => { /> + {/* Cancel shortcut is disabled on Linux due to instability with dynamic shortcut registration */} diff --git a/src/components/settings/debug/RecordingBuffer.tsx b/src/components/settings/debug/RecordingBuffer.tsx new file mode 100644 index 0000000..4bfc0f0 --- /dev/null +++ b/src/components/settings/debug/RecordingBuffer.tsx @@ -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 = ({ + descriptionMode = "tooltip", + grouped = false, +}) => { + const { t } = useTranslation(); + const { settings, updateSetting } = useSettings(); + + const handleBufferChange = (value: number) => { + updateSetting("extra_recording_buffer_ms", value); + }; + + return ( + `${v}ms`} + /> + ); +}; diff --git a/src/components/ui/Slider.tsx b/src/components/ui/Slider.tsx index 9755ac0..aaa50bc 100644 --- a/src/components/ui/Slider.tsx +++ b/src/components/ui/Slider.tsx @@ -63,7 +63,7 @@ export const Slider: React.FC = ({ }} /> {showValue && ( - + {formatValue(value)} )} diff --git a/src/i18n/locales/ar/translation.json b/src/i18n/locales/ar/translation.json index 2da0536..711a50b 100644 --- a/src/i18n/locales/ar/translation.json +++ b/src/i18n/locales/ar/translation.json @@ -462,6 +462,10 @@ "pasteDelay": { "title": "تأخير اللصق", "description": "التأخير قبل إرسال ضغطة مفتاح اللصق (بالمللي ثانية). قم بزيادتها إذا تم لصق نص خاطئ." + }, + "recordingBuffer": { + "title": "مخزن التسجيل الإضافي", + "description": "وقت إضافي (بالمللي ثانية) للاستمرار في التسجيل بعد تحرير المفتاح، لالتقاط الصوت المتبقي. 0 = لا مخزن إضافي." } }, "about": { diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 2d95c1f..b5cb99c 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -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": { diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index b4223fd..d567949 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -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": { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 5739244..21a5ab1 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -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": { diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 0624495..941a729 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -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": { diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index dca4d1d..0cd2694 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -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": { diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index 426ea5e..2b0d9b1 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -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": { diff --git a/src/i18n/locales/ja/translation.json b/src/i18n/locales/ja/translation.json index fc9c055..7adeef3 100644 --- a/src/i18n/locales/ja/translation.json +++ b/src/i18n/locales/ja/translation.json @@ -484,6 +484,10 @@ "pasteDelay": { "title": "貼り付け遅延", "description": "貼り付けキー送信前の遅延(ミリ秒)。間違ったテキストが貼り付けられる場合は増やしてください。" + }, + "recordingBuffer": { + "title": "追加録音バッファ", + "description": "キーを離した後に録音を続ける追加時間(ミリ秒)。末尾の音声を捕捉するため。0 = 追加バッファなし。" } }, "about": { diff --git a/src/i18n/locales/ko/translation.json b/src/i18n/locales/ko/translation.json index 8bc3463..b2a5a87 100644 --- a/src/i18n/locales/ko/translation.json +++ b/src/i18n/locales/ko/translation.json @@ -480,6 +480,10 @@ "title": "붙여넣기 지연", "description": "붙여넣기 키 입력을 보내기 전 지연 시간(밀리초). 잘못된 텍스트가 붙여넣어지면 늘리세요." }, + "recordingBuffer": { + "title": "추가 녹음 버퍼", + "description": "키를 놓은 후 추가로 녹음을 계속하는 시간(밀리초). 후행 오디오를 캡처하기 위함. 0 = 추가 버퍼 없음." + }, "paths": { "appData": "앱 데이터:", "models": "모델:", diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 4ebcdb1..3efd9d4 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -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": { diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index 51d8c49..cc28c27 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -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": { diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index ce4ec74..56e9f6e 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -484,6 +484,10 @@ "pasteDelay": { "title": "Задержка вставки", "description": "Задержка перед отправкой нажатия клавиши вставки (в миллисекундах). Увеличьте, если вставляется неправильный текст." + }, + "recordingBuffer": { + "title": "Дополнительный буфер записи", + "description": "Дополнительное время (в миллисекундах) для продолжения записи после отпускания клавиши, чтобы захватить завершающий звук. 0 = без дополнительного буфера." } }, "about": { diff --git a/src/i18n/locales/tr/translation.json b/src/i18n/locales/tr/translation.json index e5c5c1e..69e273a 100644 --- a/src/i18n/locales/tr/translation.json +++ b/src/i18n/locales/tr/translation.json @@ -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": { diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index 1482ce4..bc450e9 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -484,6 +484,10 @@ "pasteDelay": { "title": "Затримка вставки", "description": "Затримка перед надсиланням натискання клавіші вставки (у мілісекундах). Збільшіть, якщо вставляється неправильний текст." + }, + "recordingBuffer": { + "title": "Додатковий буфер запису", + "description": "Додатковий час (у мілісекундах) для продовження запису після відпускання клавіші, щоб захопити завершальний звук. 0 = без додаткового буфера." } }, "about": { diff --git a/src/i18n/locales/vi/translation.json b/src/i18n/locales/vi/translation.json index 2d662b6..6d3ecc2 100644 --- a/src/i18n/locales/vi/translation.json +++ b/src/i18n/locales/vi/translation.json @@ -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": { diff --git a/src/i18n/locales/zh-TW/translation.json b/src/i18n/locales/zh-TW/translation.json index cbe5218..04ed20a 100644 --- a/src/i18n/locales/zh-TW/translation.json +++ b/src/i18n/locales/zh-TW/translation.json @@ -484,6 +484,10 @@ "pasteDelay": { "title": "貼上延遲", "description": "發送貼上按鍵前的延遲(毫秒)。如果貼上了錯誤的文字,請增加此值" + }, + "recordingBuffer": { + "title": "額外錄音緩衝", + "description": "放開按鍵後繼續錄音的額外時間(毫秒),以捕捉尾音。0 = 無額外緩衝。" } }, "about": { diff --git a/src/i18n/locales/zh/translation.json b/src/i18n/locales/zh/translation.json index 6cc5049..e53e8d6 100644 --- a/src/i18n/locales/zh/translation.json +++ b/src/i18n/locales/zh/translation.json @@ -484,6 +484,10 @@ "pasteDelay": { "title": "粘贴延迟", "description": "发送粘贴按键前的延迟(毫秒)。如果粘贴了错误的文本,请增加此值。" + }, + "recordingBuffer": { + "title": "额外录音缓冲", + "description": "放开按键后继续录音的额外时间(毫秒),以捕捉尾音。0 = 无额外缓冲。" } }, "about": { diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index 325c06e..1a45824 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -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()(