mum_cli/
audio.rs

1//! All things audio.
2//!
3//! Audio is handled mostly as signals from [dasp_signal]. Input/output is handled by [cpal].
4
5pub mod input;
6pub mod output;
7pub mod sound_effects;
8pub mod transformers;
9
10use crate::error::AudioError;
11use crate::network::VoiceStreamType;
12use crate::state::StatePhase;
13
14use futures_util::stream::Stream;
15use futures_util::StreamExt;
16use mumble_protocol::voice::{VoicePacket, VoicePacketPayload};
17use mumble_protocol::Serverbound;
18use mumlib::config::SoundEffect;
19use std::collections::{hash_map::Entry, HashMap};
20use std::fmt::Debug;
21use std::sync::{Arc, Mutex};
22use tokio::sync::watch;
23
24use self::input::{AudioInputDevice, DefaultAudioInputDevice};
25use self::output::{AudioOutputDevice, ClientStream, DefaultAudioOutputDevice};
26use self::sound_effects::NotificationEvents;
27
28/// The sample rate used internally.
29const SAMPLE_RATE: u32 = 48000;
30
31/// Input audio state. Input audio is picket up from an [AudioInputDevice] (e.g.
32/// a microphone) and sent over the network.
33pub struct AudioInput {
34    device: DefaultAudioInputDevice,
35
36    /// Outgoing voice packets that should be sent over the network.
37    channel_receiver:
38        Arc<tokio::sync::Mutex<Box<dyn Stream<Item = VoicePacket<Serverbound>> + Unpin>>>,
39}
40
41impl AudioInput {
42    pub fn new(
43        input_volume: f32,
44        disable_noise_gate: bool,
45        phase_watcher: watch::Receiver<StatePhase>,
46    ) -> Result<Self, AudioError> {
47        let mut default =
48            DefaultAudioInputDevice::new(input_volume, disable_noise_gate, phase_watcher, 4)?;
49
50        let opus_stream = default
51            .sample_receiver()
52            .unwrap()
53            .enumerate()
54            .map(|(i, e)| VoicePacket::Audio {
55                _dst: std::marker::PhantomData,
56                target: 0,      // normal speech
57                session_id: (), // unused for server-bound packets
58                seq_num: i as u64,
59                payload: VoicePacketPayload::Opus(e.into(), false),
60                position_info: None,
61            });
62
63        default.play()?;
64
65        let res = Self {
66            device: default,
67            channel_receiver: Arc::new(tokio::sync::Mutex::new(Box::new(opus_stream))),
68        };
69        Ok(res)
70    }
71
72    pub fn receiver(
73        &self,
74    ) -> Arc<tokio::sync::Mutex<Box<dyn Stream<Item = VoicePacket<Serverbound>> + Unpin>>> {
75        Arc::clone(&self.channel_receiver)
76    }
77
78    pub fn set_volume(&self, input_volume: f32) {
79        self.device.set_volume(input_volume);
80    }
81}
82
83impl Debug for AudioInput {
84    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85        f.debug_struct("AudioInput")
86            .field("device", &self.device)
87            .field("channel_receiver", &"receiver")
88            .finish()
89    }
90}
91
92#[derive(Debug)]
93/// Audio output state. The audio is received from each client over the network,
94/// decoded, merged and finally played to an [AudioOutputDevice] (e.g. speaker,
95/// headphones, ...).
96pub struct AudioOutput {
97    device: DefaultAudioOutputDevice,
98    /// The volume and mute-status of a user ID.
99    user_volumes: Arc<Mutex<HashMap<u32, (f32, bool)>>>,
100
101    /// The client stream per user ID. A separate stream is kept for UDP and TCP.
102    ///
103    /// Shared with [DefaultAudioOutputDevice].
104    client_streams: Arc<Mutex<ClientStream>>,
105
106    /// Which sound effect should be played on an event.
107    sounds: HashMap<NotificationEvents, Vec<f32>>,
108}
109
110impl AudioOutput {
111    pub fn new(output_volume: f32) -> Result<Self, AudioError> {
112        let user_volumes = Arc::new(std::sync::Mutex::new(HashMap::new()));
113
114        let default = DefaultAudioOutputDevice::new(output_volume, Arc::clone(&user_volumes))?;
115        default.play()?;
116
117        let client_streams = default.client_streams();
118
119        let mut res = Self {
120            device: default,
121            sounds: HashMap::new(),
122            client_streams,
123            user_volumes,
124        };
125        res.load_sound_effects(&[]);
126        Ok(res)
127    }
128
129    /// Sets the sound effects according to some overrides, using some default
130    /// value if an event isn't overriden.
131    pub fn load_sound_effects(&mut self, overrides: &[SoundEffect]) {
132        self.sounds = sound_effects::load_sound_effects(overrides, self.device.num_channels());
133    }
134
135    /// Decodes a voice packet.
136    pub fn decode_packet_payload(
137        &self,
138        stream_type: VoiceStreamType,
139        session_id: u32,
140        payload: VoicePacketPayload,
141    ) {
142        self.client_streams
143            .lock()
144            .unwrap()
145            .decode_packet((stream_type, session_id), payload);
146    }
147
148    /// Sets the volume of the output device.
149    pub fn set_volume(&self, output_volume: f32) {
150        self.device.set_volume(output_volume);
151    }
152
153    /// Sets the incoming volume of a user.
154    pub fn set_user_volume(&self, id: u32, volume: f32) {
155        match self.user_volumes.lock().unwrap().entry(id) {
156            Entry::Occupied(mut entry) => {
157                entry.get_mut().0 = volume;
158            }
159            Entry::Vacant(entry) => {
160                entry.insert((volume, false));
161            }
162        }
163    }
164
165    /// Mutes another user.
166    pub fn set_mute(&self, id: u32, mute: bool) {
167        match self.user_volumes.lock().unwrap().entry(id) {
168            Entry::Occupied(mut entry) => {
169                entry.get_mut().1 = mute;
170            }
171            Entry::Vacant(entry) => {
172                entry.insert((1.0, mute));
173            }
174        }
175    }
176
177    /// Queues a sound effect.
178    pub fn play_effect(&self, effect: NotificationEvents) {
179        let samples = self.sounds.get(&effect).unwrap();
180        self.client_streams
181            .lock()
182            .unwrap()
183            .add_sound_effect(samples);
184    }
185}