@@ -10,10 +10,10 @@ use qrcode_generator::{to_image_from_str, QrCodeEcc};
1010use serde_json:: json;
1111use std:: borrow:: Cow ;
1212use std:: fs:: File ;
13- use std:: io:: { stdout, BufReader , Write } ;
13+ use std:: io:: { stdout, BufReader , Cursor , Write } ;
1414use std:: path:: { Path , PathBuf } ;
1515use std:: process:: Command ;
16- use std:: sync:: { Arc , Mutex } ;
16+ use std:: sync:: { mpsc , Arc , Mutex } ;
1717use std:: { env, fs} ;
1818use tempfile:: { tempdir, NamedTempFile } ;
1919use timers:: * ;
@@ -46,6 +46,7 @@ use clap::{Parser, Subcommand};
4646use colored:: Colorize ;
4747use cpal:: traits:: { DeviceTrait , HostTrait } ;
4848use enigo:: { Enigo , KeyboardControllable } ;
49+ use rodio:: { source:: SineWave , Decoder , Source } ;
4950use rdev:: { listen, Event } ;
5051use record:: rec;
5152use speakstream:: ss;
@@ -1214,6 +1215,99 @@ fn paste_clipboard_in_chunks(chunk_size: usize, format: &str) -> Result<String,
12141215
12151216static FAILED_TEMP_FILE : LazyLock < NamedTempFile > =
12161217 LazyLock :: new ( || temp_asset ! ( "../assets/failed.mp3" ) ) ;
1218+ static TICK_BYTES : & [ u8 ] = include_bytes ! ( "../assets/tick.mp3" ) ;
1219+ static BEEP_LOW_BYTES : & [ u8 ] = include_bytes ! ( "../assets/beep_low.mp3" ) ;
1220+ static BEEP_HIGH_BYTES : & [ u8 ] = include_bytes ! ( "../assets/beep.mp3" ) ;
1221+
1222+ fn tick_loop ( stop_rx : mpsc:: Receiver < ( ) > ) {
1223+ let tick_sink = DefaultDeviceSink :: new ( ) ;
1224+ loop {
1225+ if stop_rx. try_recv ( ) . is_ok ( ) {
1226+ tick_sink. stop ( ) ;
1227+ break ;
1228+ }
1229+ if tick_sink. empty ( ) {
1230+ let cursor = Cursor :: new ( TICK_BYTES ) ;
1231+ if let Ok ( decoder) = Decoder :: new ( BufReader :: new ( cursor) ) {
1232+ tick_sink. stop ( ) ;
1233+ tick_sink. append ( decoder) ;
1234+ } else {
1235+ tick_sink. stop ( ) ;
1236+ tick_sink. append (
1237+ SineWave :: new ( 880.0 )
1238+ . take_duration ( Duration :: from_millis ( 50 ) )
1239+ . amplify ( 0.20 ) ,
1240+ ) ;
1241+ }
1242+ }
1243+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
1244+ }
1245+ }
1246+
1247+ struct TickGuard {
1248+ stop_tx : Option < mpsc:: Sender < ( ) > > ,
1249+ handle : Option < thread:: JoinHandle < ( ) > > ,
1250+ }
1251+
1252+ impl TickGuard {
1253+ fn start ( ) -> Self {
1254+ let ( stop_tx, stop_rx) = mpsc:: channel ( ) ;
1255+ let handle = thread:: spawn ( move || tick_loop ( stop_rx) ) ;
1256+ Self {
1257+ stop_tx : Some ( stop_tx) ,
1258+ handle : Some ( handle) ,
1259+ }
1260+ }
1261+
1262+ fn stop ( & mut self ) {
1263+ if let Some ( stop_tx) = self . stop_tx . take ( ) {
1264+ let _ = stop_tx. send ( ( ) ) ;
1265+ }
1266+ if let Some ( handle) = self . handle . take ( ) {
1267+ let _ = handle. join ( ) ;
1268+ }
1269+ }
1270+ }
1271+
1272+ impl Drop for TickGuard {
1273+ fn drop ( & mut self ) {
1274+ self . stop ( ) ;
1275+ }
1276+ }
1277+
1278+ fn play_ptt_press_sound ( ) {
1279+ thread:: spawn ( || {
1280+ let sink = DefaultDeviceSink :: new ( ) ;
1281+ if let Ok ( decoder) = Decoder :: new ( BufReader :: new ( Cursor :: new ( BEEP_LOW_BYTES ) ) ) {
1282+ sink. append ( decoder) ;
1283+ sink. sleep_until_end ( ) ;
1284+ } else {
1285+ sink. append (
1286+ SineWave :: new ( 440.0 )
1287+ . take_duration ( Duration :: from_millis ( 120 ) )
1288+ . amplify ( 0.20 ) ,
1289+ ) ;
1290+ sink. sleep_until_end ( ) ;
1291+ }
1292+ } ) ;
1293+ }
1294+
1295+ fn play_ptt_release_sound ( ) {
1296+ thread:: spawn ( || {
1297+ let sink = DefaultDeviceSink :: new ( ) ;
1298+ if let Ok ( decoder) = Decoder :: new ( BufReader :: new ( Cursor :: new ( BEEP_HIGH_BYTES ) ) ) {
1299+ sink. append ( decoder) ;
1300+ sink. sleep_until_end ( ) ;
1301+ } else {
1302+ sink. append (
1303+ SineWave :: new ( 880.0 )
1304+ . take_duration ( Duration :: from_millis ( 120 ) )
1305+ . amplify ( 0.20 ) ,
1306+ ) ;
1307+ sink. sleep_until_end ( ) ;
1308+ }
1309+ } ) ;
1310+ }
12171311
12181312/// A global, lazily-initialized closure for sending paths into a channel.
12191313static PLAY_AUDIO : LazyLock < Box < dyn Fn ( & Path ) + Send + Sync > > = LazyLock :: new ( || {
@@ -1230,7 +1324,7 @@ static PLAY_AUDIO: LazyLock<Box<dyn Fn(&Path) + Send + Sync>> = LazyLock::new(||
12301324 for audio_path in audio_playing_rx. iter ( ) {
12311325 let file = std:: fs:: File :: open ( audio_path) . unwrap ( ) ;
12321326 sink. stop ( ) ;
1233- sink. append ( rodio :: Decoder :: new ( BufReader :: new ( file) ) . unwrap ( ) ) ;
1327+ sink. append ( Decoder :: new ( BufReader :: new ( file) ) . unwrap ( ) ) ;
12341328 // sink.play();
12351329 }
12361330 } ) ;
@@ -1366,6 +1460,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
13661460 // handle key press
13671461
13681462 audible_timers. stop_alarm ( ) ;
1463+ play_ptt_press_sound ( ) ;
13691464
13701465 // stop the AI voice from speaking
13711466 {
@@ -1405,6 +1500,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
14051500
14061501 // stop any alarms
14071502 audible_timers. stop_alarm ( ) ;
1503+ play_ptt_release_sound ( ) ;
14081504
14091505 // get elapsed time since recording started
14101506 let elapsed_option = match recording_start. elapsed ( ) {
@@ -1503,10 +1599,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
15031599 drop ( thread_speak_stream) ;
15041600
15051601 info ! ( "Transcribing user audio" ) ;
1506- let transcription_result = match runtime. block_on ( future:: timeout (
1602+ let mut tick_guard = TickGuard :: start ( ) ;
1603+ let transcription_result = runtime. block_on ( future:: timeout (
15071604 Duration :: from_secs ( 10 ) ,
15081605 transcribe:: transcribe ( & client, & audio_path) ,
1509- ) ) {
1606+ ) ) ;
1607+ tick_guard. stop ( ) ;
1608+
1609+ let transcription_result = match transcription_result {
15101610 Ok ( transcription_result) => transcription_result,
15111611 Err ( err) => {
15121612 println_error ( & format ! (
@@ -1992,6 +2092,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
19922092 . build ( )
19932093 . unwrap ( ) ;
19942094
2095+ let _tick_guard = TickGuard :: start ( ) ;
19952096 let mut stream = match runtime. block_on ( future:: timeout (
19962097 Duration :: from_secs ( 15 ) ,
19972098 client. chat ( ) . create_stream ( request) ,
0 commit comments