pub mod device;
pub mod sfx;
use std::
{
thread,
net::{ UdpSocket, TcpStream },
collections::{ HashMap, VecDeque },
time::
{
SystemTime,
Duration,
UNIX_EPOCH,
},
sync::
{
Arc,
Mutex,
LazyLock,
mpsc::Sender,
atomic::{ AtomicUsize, Ordering },
},
};
use cpal::
{
Host,
Stream,
Device,
StreamConfig,
SupportedStreamConfig,
SupportedStreamConfigRange,
traits::
{
DeviceTrait,
HostTrait,
StreamTrait,
},
};
use audiopus::
{
Channels,
SampleRate,
Application,
TryFrom,
coder::{ Encoder, Decoder },
};
use ringbuf::
{
HeapRb,
HeapCons,
HeapProd,
traits::
{
Split,
Producer,
Consumer,
},
};
use nnnoiseless::DenoiseState;
use gag::Gag;
use crate::
{
config,
command::{ self, Command },
options as chat_options,
network::
{
client::{ ClientEvent, VoiceUser },
voice::
{
consts,
self,
options,
VoiceCode,
VoicePacket,
client::sfx::SoundEffect,
},
},
};
struct LocalStream
{
_input: Stream,
_output: Stream,
}
struct StreamGuard
{
generation: usize,
}
struct RemoteStream
{
consumer: HeapCons<f32>, resample_pos: f32, current_sample: f32, next_sample: f32, activity_hold: usize, display_hold: usize, username: String, latencies: VecDeque<u128>, avg_latency: u128, }
pub struct PeerData
{
decoder: Decoder, producer: HeapProd<f32>, }
static LOCAL_STREAMS: LazyLock<Mutex<Option<LocalStream>>> = LazyLock::new(|| Mutex::new(None));
static CONSUMERS: LazyLock<Mutex<HashMap<usize, (RemoteStream, PeerData)>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
static LOCAL_DISPLAY_HOLD: AtomicUsize = AtomicUsize::new(0);
static AUDIO_GENERATION: AtomicUsize = AtomicUsize::new(0);
impl Drop for StreamGuard
{
fn drop(&mut self)
{
if AUDIO_GENERATION.load(Ordering::Relaxed) == self.generation
{
if let Ok(mut streams) = LOCAL_STREAMS.lock()
{
*streams = None;
}
}
}
}
fn find_devices(host: &Host) -> Option<(Device, Device)> {
let input_device = config::read_config::<String>("input_device");
let output_device = config::read_config::<String>("output_device");
let input_device = if !input_device.is_empty()
{
host.input_devices().ok()?.find(|d| d.description().is_ok_and(|desc| desc.to_string() == input_device))
} else { host.default_input_device() };
let output_device = if !output_device.is_empty()
{
host.output_devices().ok()?.find(|d| d.description().is_ok_and(|desc| desc.to_string() == output_device))
} else { host.default_output_device() };
Some((input_device?, output_device?))
}
fn configure_device(supported_configs: impl Iterator<Item = SupportedStreamConfigRange>, default_config: SupportedStreamConfig) -> StreamConfig
{
supported_configs
.filter(|c| c.min_sample_rate() <= consts::SAMPLE_RATE && c.max_sample_rate() >= consts::SAMPLE_RATE)
.next()
.map(|c| c.with_sample_rate(consts::SAMPLE_RATE))
.unwrap_or(default_config)
.into()
}
fn transmit_audio(encoder: &Encoder, frame: &[f32], buffer: &mut [u8], id: usize, socket: &UdpSocket)
{
if let Ok(len) = encoder.encode_float(&frame, buffer)
{
voice::send(socket, VoicePacket
{
voice: Some(buffer[..len].to_vec()),
id: Some(id),
..Default::default()
}, &chat_options::get_keys().unwrap()).unwrap();
}
}
pub fn listen_server_voice (
id: usize,
username: String,
tx: Sender<ClientEvent>,
write_stream: Arc<Mutex<TcpStream>>
)
{
options::set_seq(0);
options::set_server_seq(0);
let current_generation = AUDIO_GENERATION.fetch_add(1, Ordering::Relaxed) + 1;
let _guard = StreamGuard { generation: current_generation };
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").expect("Binding UDP failed"));
socket.connect(chat_options::get_server_address()).expect("Connecting to server UDP failed");
socket.set_read_timeout(Some(Duration::from_millis(200))).expect("Setting socket timeout failed");
let host = cpal::default_host();
let stderr_gag = Gag::stderr().unwrap();
let (input_device, output_device) = match find_devices(&host)
{
Some((input, output)) => (input, output), _ => {
command::send_command_code(&mut write_stream.lock().unwrap(), &Command::Voice, &None);
return;
}
};
drop(stderr_gag);
voice::send(&socket, VoicePacket
{
id: Some(id),
..Default::default()
}, &chat_options::get_keys().unwrap()).unwrap();
let input_config = configure_device(input_device.supported_input_configs().unwrap(), input_device.default_input_config().unwrap());
let output_config = configure_device(output_device.supported_output_configs().unwrap(), output_device.default_output_config().unwrap());
let opus_encoder = Encoder::new
(
<SampleRate as TryFrom<i32>>::try_from(consts::SAMPLE_RATE as i32).unwrap(),
Channels::Mono,
Application::Voip
).unwrap();
let mut input_accum: Vec<f32> = Vec::with_capacity(consts::FRAME_SIZE * 2);
let mut encoded_buffer = [0u8; 1500];
let input_channels = input_config.channels as usize;
let input_source_rate = input_config.sample_rate as f32;
let input_target_rate = consts::SAMPLE_RATE as f32;
let input_resample_step = input_source_rate / input_target_rate;
let mut input_resample_pos = 0.;
let gate_open = Arc::new(Mutex::new(false)); let preroll_buffer = Arc::new(Mutex::new(VecDeque::<Vec<f32>>::with_capacity(3))); let hold_frames_remaining = Arc::new(Mutex::new(0usize));
let mut denoiser = DenoiseState::new();
let mut denoise_buffer = [0.0f32; consts::SAMPLE_RATE as usize / 100];
let send_socket = socket.clone();
let input_stream = input_device.build_input_stream(&input_config, move |data: &[f32], _: &_|
{
if chat_options::is_muted(None)
{
LOCAL_DISPLAY_HOLD.store(0, Ordering::Relaxed); input_accum.clear();
if let Ok(mut gate) = gate_open.lock()
{
*gate = false;
}
if let Ok(mut preroll) = preroll_buffer.lock()
{
preroll.clear();
}
return;
}
if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
let frames_in_buffer = data.len() / input_channels;
let current_hold = LOCAL_DISPLAY_HOLD.load(Ordering::Relaxed);
if current_hold > 0
{
LOCAL_DISPLAY_HOLD.store(current_hold.saturating_sub(frames_in_buffer), Ordering::Relaxed);
}
let get_mono_sample = |index: usize| -> f32
{
if index >= frames_in_buffer { return 0. }
let mut sum = 0.;
for c in 0..input_channels
{
sum += data[index * input_channels + c];
}
sum / input_channels as f32
};
while input_resample_pos < (frames_in_buffer as f32) - 1.
{
let idx = input_resample_pos.floor() as usize;
let frac = input_resample_pos - idx as f32;
let s0 = get_mono_sample(idx);
let s1 = get_mono_sample(idx + 1);
let interpolated = s0 + (s1 - s0) * frac;
input_accum.push(interpolated);
input_resample_pos += input_resample_step;
}
input_resample_pos -= frames_in_buffer as f32;
while input_accum.len() >= consts::FRAME_SIZE
{
let mut frame: Vec<f32> = input_accum.drain(0..consts::FRAME_SIZE).collect();
for chunk in frame.chunks_mut(consts::SAMPLE_RATE as usize / 100)
{
if chunk.len() == consts::SAMPLE_RATE as usize / 100
{
for sample in chunk.iter_mut()
{
*sample *= 32767.;
}
denoiser.process_frame(&mut denoise_buffer, chunk);
for (i, sample) in denoise_buffer.iter().enumerate()
{
chunk[i] = sample / 32767.;
}
}
}
let rms = (frame.iter().map(|&x| x * x).sum::<f32>() / frame.len() as f32 + 1e-10).sqrt(); let mut gate = gate_open.lock().unwrap();
let mut preroll = preroll_buffer.lock().unwrap();
let mut hold_frames = hold_frames_remaining.lock().unwrap();
if !*gate && rms > consts::TRESHOLD_OPEN
{
*gate = true;
for old_frame in preroll.iter()
{
transmit_audio(&opus_encoder, old_frame, &mut encoded_buffer, id, &send_socket);
}
preroll.clear();
*hold_frames = consts::HOLD_FRAMES;
} else if *gate && rms < consts::TRESHOLD_CLOSE
{
if *hold_frames > 0 {
*hold_frames -= 1;
} else {
*gate = false;
}
} else if *gate && rms >= consts::TRESHOLD_CLOSE {
*hold_frames = consts::HOLD_FRAMES;
}
if !*gate
{
preroll.push_back(frame.clone());
if preroll.len() > 3
{
preroll.pop_front();
}
}
if *gate
{
LOCAL_DISPLAY_HOLD.store((consts::SAMPLE_RATE * consts::DISPLAY_HOLD as u32 / 1000) as usize, Ordering::Relaxed);
transmit_audio(&opus_encoder, &frame, &mut encoded_buffer, id, &send_socket);
}
}
}, |_| {}, None).unwrap();
let output_channels = output_config.channels as usize;
let output_source_rate = consts::SAMPLE_RATE as f32;
let output_target_rate = output_config.sample_rate as f32;
let output_resample_step = output_source_rate / output_target_rate;
let output_stream = output_device.build_output_stream(&output_config, move |data: &mut [f32], _: &_|
{
if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
data.fill(0.);
let frames_to_write = data.len() / output_channels;
let mut consumers_guard = CONSUMERS.lock().unwrap();
for i in 0..frames_to_write
{
let mut mixed_sample = 0.;
let mut active_speakers = 0;
for (stream, _) in consumers_guard.values_mut()
{
while stream.resample_pos >= 1.
{
stream.current_sample = stream.next_sample;
stream.next_sample = stream.consumer.try_pop().unwrap_or(0.); stream.resample_pos -= 1.;
}
let interpolated = stream.current_sample + (stream.next_sample - stream.current_sample) * stream.resample_pos;
stream.resample_pos += output_resample_step;
if interpolated.abs() > consts::MIXING_TRESHOLD
{
stream.activity_hold = consts::ACTIVITY_HOLD; stream.display_hold = (consts::SAMPLE_RATE * consts::DISPLAY_HOLD as u32 / 1000) as usize; }
if stream.activity_hold > 0
{
mixed_sample += interpolated;
active_speakers += 1;
stream.activity_hold -= 1;
}
if stream.display_hold > 0
{
stream.display_hold -= 1;
}
}
if active_speakers > 1
{
mixed_sample /= (active_speakers as f32).sqrt();
}
sfx::play_effects(&mut mixed_sample);
mixed_sample = mixed_sample.tanh();
for channel in 0..output_channels
{
data[i * output_channels + channel] = mixed_sample;
}
}
}, |_| {}, None).unwrap();
input_stream.play().unwrap(); output_stream.play().unwrap();
*LOCAL_STREAMS.lock().unwrap() = Some(LocalStream
{
_input: input_stream,
_output: output_stream,
});
sfx::clear_effects();
sfx::queue_effect(SoundEffect::Join);
let vad_socket = socket.clone();
thread::spawn(move ||
{
let mut iteration_counter = 0u8;
loop
{
if !options::get_use_voice()
{
tx.send(ClientEvent::VoiceActivity(Vec::new())).unwrap(); return;
}
iteration_counter += 1;
display_active_speakers(&username, &tx);
if iteration_counter == 10
{
voice::send(&vad_socket, VoicePacket
{
id: Some(id),
code: Some(VoiceCode::PING),
timestamp: Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()),
..Default::default()
}, &chat_options::get_keys().unwrap()).unwrap();
iteration_counter = 0;
}
thread::sleep(Duration::from_millis(100));
}
});
let mut decoded_buffer = [0.0f32; consts::FRAME_SIZE];
loop
{
let (network_buffer, _) = match voice::receive(&socket)
{
Some(r) => r,
None => {
if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
sfx::queue_effect(SoundEffect::Leave);
while sfx::is_playing()
{
if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
thread::sleep(Duration::from_millis(50));
}
return;
}
};
if network_buffer.seq <= options::get_server_seq() { continue; } options::set_server_seq(network_buffer.seq);
let sender_id = match network_buffer.id
{
Some(id) => id,
None => continue
};
if !CONSUMERS.lock().unwrap().contains_key(&sender_id)
{
add_consumer(sender_id, network_buffer.username.unwrap());
}
if let Some((stream, peer)) = CONSUMERS.lock().unwrap().get_mut(&sender_id)
{
if let Some(code) = network_buffer.code && let Some(timestamp) = network_buffer.timestamp
{
match code
{
VoiceCode::PING =>
{
voice::send(&socket, VoicePacket
{
id: Some(id),
target_id: Some(sender_id),
code: Some(VoiceCode::PONG),
timestamp: Some(timestamp),
..Default::default()
}, &chat_options::get_keys().unwrap()).unwrap();
},
VoiceCode::PONG =>
{
let latency = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis().saturating_sub(timestamp);
stream.latencies.push_back(latency);
if stream.latencies.len() > 20 {
stream.latencies.pop_front();
}
let sum: u128 = stream.latencies.iter().sum();
if !stream.latencies.is_empty()
{
stream.avg_latency = sum / stream.latencies.len() as u128;
}
}
}
}
if network_buffer.voice.is_none() { continue; }
if chat_options::is_muted(Some(sender_id)) { continue; }
if let Ok(decoded_len) = peer.decoder.decode_float(network_buffer.voice.as_deref(), &mut decoded_buffer[..], false)
{
peer.producer.push_slice(&decoded_buffer[..decoded_len]);
}
}
}
}
pub fn remove_consumer(id: &usize)
{
if CONSUMERS.lock().unwrap().remove(id).is_some()
{
sfx::queue_effect(SoundEffect::Leave);
}
}
pub fn remove_all_consumers()
{
CONSUMERS.lock().unwrap().clear();
sfx::queue_effect(SoundEffect::Join);
}
pub fn add_consumer(id: usize, username: String)
{
let decoder = Decoder::new
(
<SampleRate as TryFrom<i32>>::try_from(consts::SAMPLE_RATE as i32).unwrap(),
Channels::Mono,
).unwrap();
let rb = HeapRb::<f32>::new(consts::FRAME_SIZE * consts::JITTER_BUFFER_SIZE);
let (producer, mut consumer) = rb.split();
let first_sample = consumer.try_pop().unwrap_or(0.0);
CONSUMERS.lock().unwrap().insert(id, (RemoteStream
{
consumer: consumer,
resample_pos: 0.,
current_sample: 0.,
next_sample: first_sample,
activity_hold: 0,
display_hold: 0,
username: username,
latencies: VecDeque::with_capacity(20),
avg_latency: 0,
}, PeerData
{
decoder: decoder,
producer: producer,
}));
sfx::queue_effect(SoundEffect::Join);
}
fn display_active_speakers(local_username: &str, tx: &Sender<ClientEvent>)
{
let mut users_to_display = Vec::new();
let local_speaking = LOCAL_DISPLAY_HOLD.load(Ordering::Relaxed) > 0;
users_to_display.push(VoiceUser
{
id: 0,
username: local_username.to_string(),
is_speaking: local_speaking,
latency: 0,
is_local: true,
});
if let Ok(consumers) = CONSUMERS.try_lock()
{
for (id, (stream, _)) in consumers.iter()
{
users_to_display.push(VoiceUser
{
id: *id,
username: stream.username.clone(),
is_speaking: stream.display_hold > 0, latency: stream.avg_latency,
is_local: false,
});
}
}
if users_to_display.len() > 1
{
users_to_display[1..].sort_by_key(|u| u.id);
}
tx.send(ClientEvent::VoiceActivity(users_to_display)).unwrap();
}