why2_chat/network/voice/client/sfx/
mod.rs1use std::
20{
21 io::Cursor,
22 sync::
23 {
24 Arc,
25 Mutex,
26 LazyLock,
27 },
28};
29
30use lewton::inside_ogg::OggStreamReader;
31
32use crate::network::voice::options;
33
34struct ActiveEffect
36{
37 buffer: Arc<Vec<f32>>,
38 position: usize,
39}
40
41pub enum SoundEffect
43{
44 Join,
45 Leave,
46}
47
48static ACTIVE_EFFECTS: LazyLock<Mutex<Vec<ActiveEffect>>> = LazyLock::new(|| Mutex::new(Vec::new()));
50
51pub static JOIN_SOUND: LazyLock<Arc<Vec<f32>>> = LazyLock::new(|| {
54 let bytes = include_bytes!("./join.ogg");
55 Arc::new(decode_ogg_from_bytes(bytes))
56});
57
58pub static LEAVE_SOUND: LazyLock<Arc<Vec<f32>>> = LazyLock::new(|| {
60 let bytes = include_bytes!("./leave.ogg");
61 Arc::new(decode_ogg_from_bytes(bytes))
62});
63
64fn decode_ogg_from_bytes(bytes: &[u8]) -> Vec<f32> {
68 let cursor = Cursor::new(bytes);
69 let mut reader = OggStreamReader::new(cursor).expect("Failed to read ogg");
70
71 let mut samples = Vec::new();
72 let channels = reader.ident_hdr.audio_channels as usize;
73
74 while let Ok(Some(packet)) = reader.read_dec_packet_itl()
76 {
77 for chunk in packet.chunks(channels)
79 {
80 let mut sum = 0.0;
81 for sample in chunk
82 {
83 sum += *sample as f32;
84 }
85
86 samples.push((sum / channels as f32) / 32768.0);
87 }
88 }
89 samples
90}
91
92pub fn queue_effect(effect: SoundEffect) {
95 let sound = match effect
96 {
97 SoundEffect::Join => JOIN_SOUND.clone(),
98 SoundEffect::Leave => LEAVE_SOUND.clone(),
99 };
100
101 if let Ok(mut effects) = ACTIVE_EFFECTS.lock()
102 {
103 effects.push(ActiveEffect
104 {
105 buffer: sound,
106 position: 0,
107 });
108 }
109}
110
111pub fn clear_effects() {
113 if let Ok(mut effects) = ACTIVE_EFFECTS.lock()
114 {
115 effects.clear();
116 }
117}
118
119pub fn play_effects(mixed_sample: &mut f32) {
121 if let Ok(mut effects) = ACTIVE_EFFECTS.try_lock()
122 {
123 effects.retain_mut(|effect|
124 {
125 if effect.position < effect.buffer.len() {
127 *mixed_sample += effect.buffer[effect.position] * options::SOUND_EFFECT_VOLUME;
128 effect.position += 1;
129
130 true
131 } else { false } });
133 }
134}
135
136pub fn is_playing() -> bool {
138 ACTIVE_EFFECTS.lock().map(|e| !e.is_empty()).unwrap_or(false)
139}