Skip to content

Commit cdcb7b3

Browse files
committed
ticking sounds
1 parent 8557840 commit cdcb7b3

File tree

1 file changed

+106
-5
lines changed

1 file changed

+106
-5
lines changed

src/main.rs

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ use qrcode_generator::{to_image_from_str, QrCodeEcc};
1010
use serde_json::json;
1111
use std::borrow::Cow;
1212
use std::fs::File;
13-
use std::io::{stdout, BufReader, Write};
13+
use std::io::{stdout, BufReader, Cursor, Write};
1414
use std::path::{Path, PathBuf};
1515
use std::process::Command;
16-
use std::sync::{Arc, Mutex};
16+
use std::sync::{mpsc, Arc, Mutex};
1717
use std::{env, fs};
1818
use tempfile::{tempdir, NamedTempFile};
1919
use timers::*;
@@ -46,6 +46,7 @@ use clap::{Parser, Subcommand};
4646
use colored::Colorize;
4747
use cpal::traits::{DeviceTrait, HostTrait};
4848
use enigo::{Enigo, KeyboardControllable};
49+
use rodio::{source::SineWave, Decoder, Source};
4950
use rdev::{listen, Event};
5051
use record::rec;
5152
use speakstream::ss;
@@ -1214,6 +1215,99 @@ fn paste_clipboard_in_chunks(chunk_size: usize, format: &str) -> Result<String,
12141215

12151216
static 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.
12191313
static 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

Comments
 (0)