Skip to main content

why2_chat/network/voice/client/sfx/
mod.rs

1/*
2This is part of WHY2
3Copyright (C) 2022-2026 Václav Šmejkal
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use 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
34//STRUCTS
35struct ActiveEffect
36{
37    buffer: Arc<Vec<f32>>,
38    position: usize,
39}
40
41//ENUMS
42pub enum SoundEffect
43{
44    Join,
45    Leave,
46}
47
48//GLOBAL VARIABLES
49static ACTIVE_EFFECTS: LazyLock<Mutex<Vec<ActiveEffect>>> = LazyLock::new(|| Mutex::new(Vec::new()));
50
51//ASSETS
52pub static JOIN_SOUND: LazyLock<Arc<Vec<f32>>> = LazyLock::new(|| //DI DING
53{
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(|| //(DI DING).rev()
59{
60    let bytes = include_bytes!("./leave.ogg");
61    Arc::new(decode_ogg_from_bytes(bytes))
62});
63
64//FUNCTIONS
65//PRIVATE
66fn decode_ogg_from_bytes(bytes: &[u8]) -> Vec<f32> //DECODING HELPER
67{
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    //LOAD PACKETS
75    while let Ok(Some(packet)) = reader.read_dec_packet_itl()
76    {
77        //DOWNMIX TO MONO
78        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
92//PUBLIC
93pub fn queue_effect(effect: SoundEffect) //ADD SOUND TO ACTIVE_EFFECTS
94{
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() //STOP ALL SOUNDS
112{
113    if let Ok(mut effects) = ACTIVE_EFFECTS.lock()
114    {
115        effects.clear();
116    }
117}
118
119pub fn play_effects(mixed_sample: &mut f32) //PLAY ALL SOUND IN ACTIVE_EFFECTS
120{
121    if let Ok(mut effects) = ACTIVE_EFFECTS.try_lock()
122    {
123        effects.retain_mut(|effect|
124        {
125            if effect.position < effect.buffer.len() //SOUND STILL PLAYING
126            {
127                *mixed_sample += effect.buffer[effect.position] * options::SOUND_EFFECT_VOLUME;
128                effect.position += 1;
129
130                true
131            } else { false } //SOUND ENDED, REMOVE
132        });
133    }
134}
135
136pub fn is_playing() -> bool //CHECK IF ANY SOUND EFFECT IS QUEUED
137{
138    ACTIVE_EFFECTS.lock().map(|e| !e.is_empty()).unwrap_or(false)
139}