Skip to content

Commit 0c5a0d0

Browse files
committed
phone bell works now
1 parent 8737a3e commit 0c5a0d0

File tree

4 files changed

+113
-52
lines changed

4 files changed

+113
-52
lines changed

Software/phone-bell.service

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
[Unit]
22
Description=Phone Bell Service
3-
After=network.target
3+
After=network.target sound.target
44
StartLimitIntervalSec=0
55

66
[Service]
77
Type=simple
88
Restart=always
99
RestartSec=1
1010
User=hackers
11-
ExecStart=/home/hackers/phone-bell-software
11+
WorkingDirectory=/home/hackers/phone-bell/Software
12+
EnvironmentFile=/home/hackers/phone-bell/Software/.env
13+
Environment=XDG_RUNTIME_DIR=/run/user/1000
14+
ExecStart=/home/hackers/phone-bell/Software/target/release/phone-bell-software
1215

1316
[Install]
1417
WantedBy=default.target

Software/src/hardware/audio.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use std::{
2-
sync::mpsc::{channel, Receiver, Sender, TryRecvError},
2+
sync::{
3+
atomic::{AtomicBool, Ordering},
4+
mpsc::{channel, Receiver, Sender, TryRecvError},
5+
Arc,
6+
},
37
thread,
48
time::Duration,
59
};
@@ -64,12 +68,16 @@ pub enum StreamKind {
6468
pub struct AudioSystemMarshaller {
6569
from_input: Receiver<Vec<f32>>,
6670
to_output: Sender<Vec<f32>>,
71+
recording: Arc<AtomicBool>,
6772
}
6873

6974
impl AudioSystemMarshaller {
7075
pub fn create() -> Self {
7176
let (input, from_input) = channel();
7277
let (to_output, output) = channel::<Vec<f32>>();
78+
let recording = Arc::new(AtomicBool::new(false));
79+
let recording_flag = recording.clone();
80+
7381
thread::spawn(move || {
7482
let mut audio_system = AudioSystem::create();
7583

@@ -78,13 +86,15 @@ impl AudioSystemMarshaller {
7886
let output_ready = audio_system.is_output_ready();
7987

8088
if input_ready {
89+
// Always drain the cpal channel to prevent buildup,
90+
// but only forward samples when recording is enabled
8191
if let Ok(s) = audio_system.read_next_samples() {
82-
if !s.is_empty() {
92+
if !s.is_empty() && recording_flag.load(Ordering::Relaxed) {
8393
input.send(s).unwrap();
8494
}
8595
}
8696
}
87-
if let Ok(r) = output.try_recv() {
97+
while let Ok(r) = output.try_recv() {
8898
if output_ready {
8999
audio_system.write_next_samples(r.as_slice()).unwrap();
90100
}
@@ -94,18 +104,23 @@ impl AudioSystemMarshaller {
94104
// Audio not set up yet, wait before retrying
95105
thread::sleep(Duration::from_secs(2));
96106
} else {
97-
// Sleep ~20ms to collect one Opus frame worth of samples
98-
thread::sleep(Duration::from_millis(20));
107+
// Short sleep to keep output fed continuously
108+
thread::sleep(Duration::from_millis(5));
99109
}
100110
}
101111
});
102112

103113
Self {
104114
from_input,
105115
to_output,
116+
recording,
106117
}
107118
}
108119

120+
pub fn set_recording(&self, enabled: bool) {
121+
self.recording.store(enabled, Ordering::Relaxed);
122+
}
123+
109124
pub fn send_to_speaker(&self, data: Vec<f32>) {
110125
self.to_output.send(data).unwrap();
111126
}
@@ -417,9 +432,10 @@ impl AudioSystem {
417432
_output_callback_info: &cpal::OutputCallbackInfo,
418433
audio_buffer_reference: &Receiver<f32>,
419434
) {
435+
const VOLUME_MULTIPLIER: f32 = 0.2;
420436
for sample in data.iter_mut() {
421437
match audio_buffer_reference.try_recv() {
422-
Ok(sample_value) => *sample = T::from_sample(sample_value),
438+
Ok(sample_value) => *sample = T::from_sample(sample_value * VOLUME_MULTIPLIER),
423439
Err(_) => *sample = Sample::EQUILIBRIUM,
424440
}
425441
}

Software/src/network/iroh_voip.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub struct PhoneIroh {
1919
peer_addr_receiver: Receiver<String>,
2020
our_addr_sender: Sender<String>,
2121
muted: bool,
22+
mic_buffer: Vec<f32>,
2223
}
2324

2425
impl PhoneIroh {
@@ -35,6 +36,7 @@ impl PhoneIroh {
3536
peer_addr_receiver,
3637
our_addr_sender,
3738
muted: true,
39+
mic_buffer: Vec::new(),
3840
};
3941

4042
(iroh, mute_sender)
@@ -68,11 +70,22 @@ impl PhoneIroh {
6870
// Poll sync channels (mute + peer address)
6971
while let Ok(mute) = self.mute_receiver.try_recv() {
7072
self.muted = mute;
73+
audio_system.set_recording(!mute && self.active_connection.is_some());
74+
if mute {
75+
self.mic_buffer.clear();
76+
}
7177
println!("Mute state changed: {}", mute);
7278
}
7379

7480
while let Ok(peer_addr_str) = self.peer_addr_receiver.try_recv() {
7581
println!("Received peer address: {}...", &peer_addr_str[..16.min(peer_addr_str.len())]);
82+
// Close any existing connection so we can connect to the new peer
83+
// (prevents stale connections from blocking new ones)
84+
if let Some(conn) = self.active_connection.take() {
85+
conn.close(0u32.into(), b"new peer");
86+
audio_system.set_recording(false);
87+
println!("Closed existing connection for new peer");
88+
}
7689
pending_peer = Some(peer_addr_str);
7790
}
7891

@@ -81,19 +94,23 @@ impl PhoneIroh {
8194
if conn.close_reason().is_some() {
8295
println!("Connection closed");
8396
self.active_connection = None;
97+
audio_system.set_recording(false);
8498
}
8599
}
86100

87101
if self.active_connection.is_some() {
88102
// Connected: send/receive audio
89103
let conn = self.active_connection.as_ref().unwrap();
90104

91-
// Send audio if not muted
92-
if !self.muted {
93-
if let Ok(samples) = audio_system.try_receive_from_mic() {
94-
if let Err(e) = self.send_audio(&encoder, conn, &samples) {
95-
eprintln!("Failed to send audio: {}", e);
96-
}
105+
// Drain all available mic samples into the buffer
106+
while let Ok(samples) = audio_system.try_receive_from_mic() {
107+
self.mic_buffer.extend_from_slice(&samples);
108+
}
109+
// Send complete Opus frames
110+
while self.mic_buffer.len() >= OPUS_FRAME_SIZE {
111+
let frame: Vec<f32> = self.mic_buffer.drain(..OPUS_FRAME_SIZE).collect();
112+
if let Err(e) = self.send_audio(&encoder, conn, &frame) {
113+
eprintln!("Failed to send audio: {}", e);
97114
}
98115
}
99116

@@ -120,6 +137,7 @@ impl PhoneIroh {
120137
Ok(conn) => {
121138
println!("Connected to peer: {}", conn.remote_id().fmt_short());
122139
self.active_connection = Some(conn);
140+
audio_system.set_recording(!self.muted);
123141
pending_peer = None;
124142
}
125143
Err(e) => {
@@ -134,6 +152,7 @@ impl PhoneIroh {
134152
Ok(conn) => {
135153
println!("Accepted connection from: {}", conn.remote_id().fmt_short());
136154
self.active_connection = Some(conn);
155+
audio_system.set_recording(!self.muted);
137156
pending_peer = None;
138157
}
139158
Err(e) => {
@@ -162,6 +181,7 @@ impl PhoneIroh {
162181
conn.remote_id().fmt_short()
163182
);
164183
self.active_connection = Some(conn);
184+
audio_system.set_recording(!self.muted);
165185
}
166186
Err(e) => {
167187
eprintln!("Failed to accept connection: {}", e);

Software/src/ui.rs

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -50,54 +50,68 @@ pub async fn ui_entry(
5050
if *hardware.dialed_number() != last_dialed_number
5151
&& !hardware.dialed_number().is_empty()
5252
{
53-
let mut contains = false;
54-
55-
for number in KNOWN_NUMBERS {
56-
if number == hardware.dialed_number() {
57-
contains = true;
53+
if in_call {
54+
// In-call DTMF: forward the latest digit to the server
55+
if let Some(digit) = hardware.dialed_number().chars().last() {
56+
println!("In-call dial: {}", digit);
57+
let _ = network_sender.send(PhoneOutgoingMessage::Dial {
58+
number: digit.to_string(),
59+
});
5860
}
59-
}
61+
hardware.dialed_number().clear();
62+
} else {
63+
// Normal number matching for initiating a call
64+
let mut contains = false;
6065

61-
if !contains {
6266
for number in KNOWN_NUMBERS {
63-
if number.starts_with(&*hardware.dialed_number()) {
67+
if number == hardware.dialed_number() {
6468
contains = true;
6569
}
6670
}
6771

6872
if !contains {
69-
*hardware.dialed_number() = String::from("0");
70-
}
71-
72-
contains = !contains;
73-
}
73+
for number in KNOWN_NUMBERS {
74+
if number.starts_with(&*hardware.dialed_number()) {
75+
contains = true;
76+
}
77+
}
7478

75-
if contains {
76-
hardware.enable_dialing(false);
79+
if !contains {
80+
*hardware.dialed_number() = String::from("0");
81+
}
7782

78-
in_call = true;
83+
contains = !contains;
84+
}
7985

80-
println!("Calling: {}", hardware.dialed_number());
81-
let _ = network_sender.send(PhoneOutgoingMessage::Dial {
82-
number: hardware.dialed_number().clone(),
83-
});
84-
85-
silent_ring = hardware.dialed_number() == "7";
86-
87-
if hook_state {
88-
hardware.ring(true);
89-
} else {
90-
// Unmute immediately; server will also send Mute(false)
91-
let _ = mute_sender.send(false);
92-
sink.clear();
93-
94-
if !silent_ring {
95-
// ! REMOVE THIS LATER
96-
let client = reqwest::Client::new();
97-
let _ = client
98-
.post("https://api.purduehackers.com/doorbell/ring")
99-
.send()
100-
.await;
86+
if contains {
87+
hardware.enable_dialing(false);
88+
89+
in_call = true;
90+
91+
println!("Calling: {}", hardware.dialed_number());
92+
let _ = network_sender.send(PhoneOutgoingMessage::Dial {
93+
number: hardware.dialed_number().clone(),
94+
});
95+
96+
silent_ring = hardware.dialed_number() == "7";
97+
98+
if hook_state {
99+
hardware.ring(true);
100+
} else {
101+
// Unmute immediately; server will also send Mute(false)
102+
let _ = mute_sender.send(false);
103+
sink.clear();
104+
105+
if !silent_ring {
106+
// ! REMOVE THIS LATER
107+
tokio::spawn(async {
108+
let client = reqwest::Client::new();
109+
let _ = client
110+
.post("https://api.purduehackers.com/doorbell/ring")
111+
.send()
112+
.await;
113+
});
114+
}
101115
}
102116
}
103117
}
@@ -130,6 +144,8 @@ pub async fn ui_entry(
130144
in_call = true;
131145
sink.clear();
132146
let _ = mute_sender.send(false);
147+
hardware.enable_dialing(true);
148+
hardware.dialed_number().clear();
133149
} else if in_call {
134150
// Picking up after on-hook dial
135151
hardware.ring(false);
@@ -183,7 +199,13 @@ pub async fn ui_entry(
183199
sink.append(source.convert_samples::<f32>());
184200
sink.play();
185201
}
186-
Sound::Ringback | Sound::Hangup | Sound::None => {
202+
Sound::None => {
203+
sink.clear();
204+
// Call connected — re-enable dialing for in-call DTMF
205+
hardware.enable_dialing(true);
206+
hardware.dialed_number().clear();
207+
}
208+
Sound::Ringback | Sound::Hangup => {
187209
// TODO: add ringback and hangup sound assets
188210
sink.clear();
189211
}

0 commit comments

Comments
 (0)