From ab79f7774748bf5370097f10c85de35ba4948461 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Fri, 27 Jun 2025 12:54:29 -0700 Subject: [PATCH] sample rec sound --- src-tauri/Cargo.lock | 110 ++++++++++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/resources/default_settings.json | 2 +- src-tauri/resources/rec.wav | Bin 0 -> 8234 bytes src-tauri/src/actions.rs | 7 ++ src-tauri/src/lib.rs | 1 + src-tauri/src/settings.rs | 2 + src-tauri/src/shortcut.rs | 8 ++ src-tauri/src/utils.rs | 55 +++++++++ src-tauri/tauri.conf.json | 6 +- src/App.tsx | 2 +- src/components/settings/KeyboardShortcuts.tsx | 27 +++++ src/components/settings/Settings.tsx | 5 +- src/lib/types.ts | 3 +- 14 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 src-tauri/resources/rec.wav diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 11397b7..8772819 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -161,6 +161,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -683,6 +689,12 @@ dependencies = [ "libloading 0.8.6", ] +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -1260,6 +1272,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -1995,6 +2016,7 @@ dependencies = [ "log", "once_cell", "rdev", + "rodio", "rubato", "serde", "serde_json", @@ -2060,6 +2082,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "html5ever" version = "0.29.1" @@ -2604,6 +2632,17 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + [[package]] name = "libappindicator" version = "0.9.0" @@ -3363,6 +3402,15 @@ dependencies = [ "cc", ] +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -4161,6 +4209,19 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" +[[package]] +name = "rodio" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" +dependencies = [ + "claxon", + "cpal", + "hound", + "lewton", + "symphonia", +] + [[package]] name = "rubato" version = "0.16.1" @@ -4743,6 +4804,55 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index dec38c2..92560e2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -42,6 +42,7 @@ tauri-plugin-store = "2" tauri-plugin-os = "2" enigo = "0.5.0" tauri-plugin-process = "2" +rodio = "0.20.1" [target.'cfg(target_os = "macos")'.dependencies] whisper-rs = { version = "0.13.2", features = ["metal"] } diff --git a/src-tauri/resources/default_settings.json b/src-tauri/resources/default_settings.json index 2dd52ed..2040dc9 100644 --- a/src-tauri/resources/default_settings.json +++ b/src-tauri/resources/default_settings.json @@ -2,7 +2,7 @@ "bindings": { "transcribe": { "id": "transcribe", - "name": "Transcribe", + "name": "Transcribe Keyboard Shortcut", "description": "Converts your speech into text.", "default_binding": "alt+space" } diff --git a/src-tauri/resources/rec.wav b/src-tauri/resources/rec.wav new file mode 100644 index 0000000000000000000000000000000000000000..5910d1f24ef965c5353d4985c9a49642505f1099 GIT binary patch literal 8234 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 B27dqm literal 0 HcmV?d00001 diff --git a/src-tauri/src/actions.rs b/src-tauri/src/actions.rs index ef6a976..205d833 100644 --- a/src-tauri/src/actions.rs +++ b/src-tauri/src/actions.rs @@ -2,6 +2,7 @@ use crate::managers::audio::AudioRecordingManager; use crate::managers::transcription::TranscriptionManager; use crate::utils; use crate::utils::change_tray_icon; +use crate::utils::play_recording_sound; use crate::utils::TrayIconState; use once_cell::sync::Lazy; use std::collections::HashMap; @@ -23,6 +24,9 @@ impl ShortcutAction for TranscribeAction { let binding_id = binding_id.to_string(); change_tray_icon(app, TrayIconState::Recording); + // Play audio feedback for recording start + play_recording_sound(app); + let rm = app.state::>(); rm.try_start_recording(&binding_id); } @@ -34,6 +38,9 @@ impl ShortcutAction for TranscribeAction { change_tray_icon(app, TrayIconState::Idle); + // Play audio feedback for recording stop + play_recording_sound(app); + let binding_id = binding_id.to_string(); // Clone binding_id for the async task tauri::async_runtime::spawn(async move { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d6a3da3..41b5c76 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -168,6 +168,7 @@ pub fn run() { shortcut::change_binding, shortcut::reset_binding, shortcut::change_ptt_setting, + shortcut::change_audio_feedback_setting, trigger_update_check ]) .run(tauri::generate_context!()) diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index 021c9be..0934b43 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -17,6 +17,7 @@ pub struct ShortcutBinding { pub struct AppSettings { pub bindings: HashMap, pub push_to_talk: bool, + pub audio_feedback: bool, } pub const SETTINGS_STORE_PATH: &str = "settings_store.json"; @@ -47,6 +48,7 @@ pub fn get_default_settings() -> AppSettings { AppSettings { bindings, push_to_talk: true, + audio_feedback: true, } } diff --git a/src-tauri/src/shortcut.rs b/src-tauri/src/shortcut.rs index 3a0b047..fae9de7 100644 --- a/src-tauri/src/shortcut.rs +++ b/src-tauri/src/shortcut.rs @@ -103,6 +103,14 @@ pub fn change_ptt_setting(app: AppHandle, enabled: bool) -> Result<(), String> { Ok(()) } +#[tauri::command] +pub fn change_audio_feedback_setting(app: AppHandle, enabled: bool) -> Result<(), String> { + let mut settings = settings::get_settings(&app); + settings.audio_feedback = enabled; + settings::write_settings(&app, settings); + Ok(()) +} + fn _register_shortcut(app: &AppHandle, binding: ShortcutBinding) -> Result<(), String> { // Parse shortcut and return error if it fails let shortcut = match binding.current_binding.parse::() { diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index c312f07..3fd7877 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,8 +1,14 @@ +use crate::settings; + use enigo::Enigo; use enigo::Key; use enigo::Keyboard; use enigo::Settings; +use rodio::{Decoder, OutputStream, Sink}; +use std::fs::File; +use std::io::BufReader; +use std::thread; use tauri::image::Image; use tauri::tray::TrayIcon; use tauri::AppHandle; @@ -80,3 +86,52 @@ pub fn change_tray_icon(app: &AppHandle, icon: TrayIconState) { .expect("failed to set icon"), )); } + +pub fn play_recording_sound(app: &AppHandle) { + // Check if audio feedback is enabled + let settings = settings::get_settings(app); + if !settings.audio_feedback { + return; + } + + let app_handle = app.clone(); + + // Spawn a new thread to play the audio without blocking the main thread + thread::spawn(move || { + // Get the path to the rec.wav file in resources + let audio_path = match app_handle + .path() + .resolve("resources/rec.wav", tauri::path::BaseDirectory::Resource) + { + Ok(path) => path, + Err(e) => { + eprintln!("Failed to resolve audio file path: {}", e); + return; + } + }; + + // Try to play the audio file + if let Err(e) = play_audio_file(&audio_path) { + eprintln!("Failed to play recording sound: {}", e); + } + }); +} + +fn play_audio_file(path: &std::path::Path) -> Result<(), Box> { + // Get a output stream handle to the default physical sound device + let (_stream, stream_handle) = OutputStream::try_default()?; + + // Load the audio file + let file = File::open(path)?; + let buf_reader = BufReader::new(file); + let source = Decoder::new(buf_reader)?; + + // Create a sink to play the audio + let sink = Sink::try_new(&stream_handle)?; + sink.append(source); + + // Wait for the audio to finish playing + sink.sleep_until_end(); + + Ok(()) +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9a7d79e..84b568b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,9 +14,9 @@ { "title": "Handy", "width": 540, - "height": 400, - "minWidth": 460, - "minHeight": 440, + "height": 510, + "minWidth": 540, + "minHeight": 510, "resizable": true, "maximizable": false } diff --git a/src/App.tsx b/src/App.tsx index 5aed574..1ecb7a0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import Footer from "./components/footer"; function App() { return (
-
+
diff --git a/src/components/settings/KeyboardShortcuts.tsx b/src/components/settings/KeyboardShortcuts.tsx index 9c5609f..df5c967 100644 --- a/src/components/settings/KeyboardShortcuts.tsx +++ b/src/components/settings/KeyboardShortcuts.tsx @@ -14,6 +14,8 @@ import ResetIcon from "../icons/ResetIcon"; export const KeyboardShortcuts: React.FC = () => { const [bindings, setBindings] = React.useState({}); const [pttEnabled, setPttEnabled] = React.useState(false); + const [audioFeedbackEnabled, setAudioFeedbackEnabled] = + React.useState(true); const [keyPressed, setKeyPressed] = useState([]); const [recordedKeys, setRecordedKeys] = useState([]); const [editingShortcutId, setEditingShortcutId] = useState( @@ -77,6 +79,7 @@ export const KeyboardShortcuts: React.FC = () => { const settings = SettingsSchema.parse(s); setBindings(settings.bindings); setPttEnabled(settings.push_to_talk); + setAudioFeedbackEnabled(settings.audio_feedback); }); }); }, []); @@ -232,6 +235,30 @@ export const KeyboardShortcuts: React.FC = () => {
+
+
+

Audio Feedback

+

Play sound when recording starts and stops

+
+ +
{Object.entries(bindings).map(([id, binding]) => (
{ return (
-
- {/*

Keyboard Shortcuts

*/} - -
+
); }; diff --git a/src/lib/types.ts b/src/lib/types.ts index 08b810f..59878a5 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -10,12 +10,13 @@ export const ShortcutBindingSchema = z.object({ export const ShortcutBindingsMapSchema = z.record( z.string(), - ShortcutBindingSchema + ShortcutBindingSchema, ); export const SettingsSchema = z.object({ bindings: ShortcutBindingsMapSchema, push_to_talk: z.boolean(), + audio_feedback: z.boolean(), }); export const BindingResponseSchema = z.object({