aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--mumd/src/audio.rs27
-rw-r--r--mumd/src/audio/input.rs15
-rw-r--r--mumd/src/audio/output.rs26
-rw-r--r--mumd/src/state/channel.rs13
-rw-r--r--mumlib/src/command.rs62
-rw-r--r--mumlib/src/config.rs56
-rw-r--r--mumlib/src/lib.rs10
-rw-r--r--mumlib/src/state.rs39
8 files changed, 229 insertions, 19 deletions
diff --git a/mumd/src/audio.rs b/mumd/src/audio.rs
index beff9cd..0424033 100644
--- a/mumd/src/audio.rs
+++ b/mumd/src/audio.rs
@@ -1,3 +1,7 @@
+//! All things audio.
+//!
+//! Audio is handled mostly as signals from [dasp_signal]. Input/output is handled by [cpal].
+
pub mod input;
pub mod output;
pub mod transformers;
@@ -27,8 +31,11 @@ use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use tokio::sync::watch;
+/// The sample rate used internally.
const SAMPLE_RATE: u32 = 48000;
+/// All types of notifications that can be shown. Each notification can be bound to its own audio
+/// file.
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, EnumIter)]
pub enum NotificationEvents {
ServerConnect,
@@ -65,9 +72,12 @@ impl TryFrom<&str> for NotificationEvents {
}
}
+/// Input audio state. Input audio is picket up from an [AudioInputDevice] (e.g.
+/// a microphone) and sent over the network.
pub struct AudioInput {
device: DefaultAudioInputDevice,
+ /// Outgoing voice packets that should be sent over the network.
channel_receiver:
Arc<tokio::sync::Mutex<Box<dyn Stream<Item = VoicePacket<Serverbound>> + Unpin>>>,
}
@@ -112,12 +122,20 @@ impl AudioInput {
}
}
+/// Audio output state. The audio is received from each client over the network,
+/// decoded, merged and finally played to an [AudioOutputDevice] (e.g. speaker,
+/// headphones, ...).
pub struct AudioOutput {
device: DefaultAudioOutputDevice,
+ /// The volume and mute-status of a user ID.
user_volumes: Arc<Mutex<HashMap<u32, (f32, bool)>>>,
+ /// The client stream per user ID. A separate stream is kept for UDP and TCP.
+ ///
+ /// Shared with [DefaultAudioOutputDevice].
client_streams: Arc<Mutex<ClientStream>>,
+ /// Which sound effect should be played on an event.
sounds: HashMap<NotificationEvents, Vec<f32>>,
}
@@ -140,6 +158,7 @@ impl AudioOutput {
Ok(res)
}
+ /// Loads sound effects, getting unspecified effects from [get_default_sfx].
pub fn load_sound_effects(&mut self, sound_effects: &[SoundEffect]) {
let overrides: HashMap<_, _> = sound_effects
.iter()
@@ -153,6 +172,7 @@ impl AudioOutput {
})
.collect();
+ // This makes sure that every [NotificationEvent] is present in [self.sounds].
self.sounds = NotificationEvents::iter()
.map(|event| {
let bytes = overrides
@@ -195,6 +215,7 @@ impl AudioOutput {
.collect();
}
+ /// Decodes a voice packet.
pub fn decode_packet_payload(
&self,
stream_type: VoiceStreamType,
@@ -207,10 +228,12 @@ impl AudioOutput {
.decode_packet((stream_type, session_id), payload);
}
+ /// Sets the volume of the output device.
pub fn set_volume(&self, output_volume: f32) {
self.device.set_volume(output_volume);
}
+ /// Sets the incoming volume of a user.
pub fn set_user_volume(&self, id: u32, volume: f32) {
match self.user_volumes.lock().unwrap().entry(id) {
Entry::Occupied(mut entry) => {
@@ -222,6 +245,7 @@ impl AudioOutput {
}
}
+ /// Mutes another user.
pub fn set_mute(&self, id: u32, mute: bool) {
match self.user_volumes.lock().unwrap().entry(id) {
Entry::Occupied(mut entry) => {
@@ -233,12 +257,14 @@ impl AudioOutput {
}
}
+ /// Queues a sound effect.
pub fn play_effect(&self, effect: NotificationEvents) {
let samples = self.sounds.get(&effect).unwrap();
self.client_streams.lock().unwrap().add_sound_effect(samples);
}
}
+/// Reads a sound effect from disk.
// moo
fn get_sfx(file: &str) -> Cow<'static, [u8]> {
let mut buf: Vec<u8> = Vec::new();
@@ -251,6 +277,7 @@ fn get_sfx(file: &str) -> Cow<'static, [u8]> {
}
}
+/// Gets the default sound effect.
fn get_default_sfx() -> Cow<'static, [u8]> {
Cow::from(include_bytes!("fallback_sfx.wav").as_ref())
}
diff --git a/mumd/src/audio/input.rs b/mumd/src/audio/input.rs
index 25d15d5..37ea60a 100644
--- a/mumd/src/audio/input.rs
+++ b/mumd/src/audio/input.rs
@@ -1,3 +1,4 @@
+//! Listens to the microphone and sends it to the networking.
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{InputCallbackInfo, Sample, SampleFormat, SampleRate, StreamConfig};
use log::*;
@@ -8,6 +9,7 @@ use crate::audio::transformers::{NoiseGate, Transformer};
use crate::error::{AudioError, AudioStream};
use crate::state::StatePhase;
+/// Generates a callback that receives [Sample]s and sends them as floats to a [futures_channel::mpsc::Sender].
pub fn callback<T: Sample>(
mut input_sender: futures_channel::mpsc::Sender<Vec<u8>>,
mut transformers: Vec<Box<dyn Transformer + Send + 'static>>,
@@ -43,11 +45,19 @@ pub fn callback<T: Sample>(
}
}
+/// Something that can listen to audio and send it somewhere.
+///
+/// One sample is assumed to be an encoded opus frame. See [opus::Encoder].
pub trait AudioInputDevice {
+ /// Starts the device.
fn play(&self) -> Result<(), AudioError>;
+ /// Stops the device.
fn pause(&self) -> Result<(), AudioError>;
+ /// Sets the input volume of the device.
fn set_volume(&self, volume: f32);
+ /// Returns a receiver to this device's values.
fn sample_receiver(&mut self) -> Option<futures_channel::mpsc::Receiver<Vec<u8>>>;
+ /// The amount of channels this device has.
fn num_channels(&self) -> usize;
}
@@ -59,6 +69,7 @@ pub struct DefaultAudioInputDevice {
}
impl DefaultAudioInputDevice {
+ /// Initializes the default audio input.
pub fn new(
input_volume: f32,
phase_watcher: watch::Receiver<StatePhase>,
@@ -162,17 +173,21 @@ impl AudioInputDevice for DefaultAudioInputDevice {
.play()
.map_err(|e| AudioError::InputPlayError(e))
}
+
fn pause(&self) -> Result<(), AudioError> {
self.stream
.pause()
.map_err(|e| AudioError::InputPauseError(e))
}
+
fn set_volume(&self, volume: f32) {
self.volume_sender.send(volume).unwrap();
}
+
fn sample_receiver(&mut self) -> Option<futures_channel::mpsc::Receiver<Vec<u8>>> {
self.sample_receiver.take()
}
+
fn num_channels(&self) -> usize {
self.channels as usize
}
diff --git a/mumd/src/audio/output.rs b/mumd/src/audio/output.rs
index cdb7b8e..b945710 100644
--- a/mumd/src/audio/output.rs
+++ b/mumd/src/audio/output.rs
@@ -1,3 +1,5 @@
+//! Receives audio packets from the networking and plays them.
+
use crate::audio::SAMPLE_RATE;
use crate::error::{AudioError, AudioStream};
use crate::network::VoiceStreamType;
@@ -16,12 +18,13 @@ use tokio::sync::watch;
type ClientStreamKey = (VoiceStreamType, u32);
+/// State for decoding audio received from another user.
pub struct ClientAudioData {
buf: Bounded<Vec<f32>>,
output_channels: opus::Channels,
- //we need two since a client can hypothetically send both mono
- //and stereo packets, and we can't switch a decoder on the fly
- //to reuse it
+ // We need both since a client can hypothetically send both mono
+ // and stereo packets, and we can't switch a decoder on the fly
+ // to reuse it.
mono_decoder: opus::Decoder,
stereo_decoder: opus::Decoder,
}
@@ -62,6 +65,7 @@ impl ClientAudioData {
}
}
+/// Collected state for client opus decoders and sound effects.
pub struct ClientStream {
buffer_clients: HashMap<ClientStreamKey, ClientAudioData>,
buffer_effects: VecDeque<f32>,
@@ -90,6 +94,7 @@ impl ClientStream {
)
}
+ /// Decodes a voice packet.
pub fn decode_packet(&mut self, client: ClientStreamKey, payload: VoicePacketPayload) {
match payload {
VoicePacketPayload::Opus(bytes, _eot) => {
@@ -101,12 +106,19 @@ impl ClientStream {
}
}
+ /// Extends the sound effect buffer queue with some received values.
pub fn add_sound_effect(&mut self, values: &[f32]) {
self.buffer_effects.extend(values.iter().copied());
}
}
+/// Adds two values in some saturating way.
+///
+/// Since we support [f32], [i16] and [u16] we need some way of adding two values
+/// without peaking above/below the edge values. This trait ensures that we can
+/// use all three primitive types as a generic parameter.
pub trait SaturatingAdd {
+ /// Adds two values in some saturating way. See trait documentation.
fn saturating_add(self, rhs: Self) -> Self;
}
@@ -140,14 +152,20 @@ pub trait AudioOutputDevice {
fn client_streams(&self) -> Arc<Mutex<ClientStream>>;
}
+/// The default audio output device, as determined by [cpal].
pub struct DefaultAudioOutputDevice {
config: StreamConfig,
stream: cpal::Stream,
+ /// The client stream per user ID. A separate stream is kept for UDP and TCP.
+ ///
+ /// Shared with [super::AudioOutput].
client_streams: Arc<Mutex<ClientStream>>,
+ /// Output volume configuration.
volume_sender: watch::Sender<f32>,
}
impl DefaultAudioOutputDevice {
+ /// Initializes the default audio output.
pub fn new(
output_volume: f32,
user_volumes: Arc<Mutex<HashMap<u32, (f32, bool)>>>,
@@ -247,6 +265,8 @@ impl AudioOutputDevice for DefaultAudioOutputDevice {
}
}
+/// Returns a function that fills a buffer with audio from client streams
+/// modified according to some audio configuration.
pub fn callback<T: Sample + AddAssign + SaturatingAdd + std::fmt::Display>(
user_bufs: Arc<Mutex<ClientStream>>,
output_volume_receiver: watch::Receiver<f32>,
diff --git a/mumd/src/state/channel.rs b/mumd/src/state/channel.rs
index 6995e1e..2ed05c5 100644
--- a/mumd/src/state/channel.rs
+++ b/mumd/src/state/channel.rs
@@ -169,13 +169,10 @@ pub fn into_channel(
impl From<&Channel> for mumlib::state::Channel {
fn from(channel: &Channel) -> Self {
- mumlib::state::Channel {
- description: channel.description.clone(),
- links: Vec::new(),
- max_users: channel.max_users,
- name: channel.name.clone(),
- children: Vec::new(),
- users: Vec::new(),
- }
+ mumlib::state::Channel::new(
+ channel.name.clone(),
+ channel.description.clone(),
+ channel.max_users,
+ )
}
}
diff --git a/mumlib/src/command.rs b/mumlib/src/command.rs
index 8cb7cb9..9d98a38 100644
--- a/mumlib/src/command.rs
+++ b/mumlib/src/command.rs
@@ -1,3 +1,6 @@
+//! [Command]s can be sent from a controller to mumd which might respond with a
+//! [CommandResponse].
+
use crate::state::{Channel, Server};
use chrono::NaiveDateTime;
@@ -7,7 +10,9 @@ use std::fmt;
/// Something that happened in our channel at a point in time.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MumbleEvent {
+ /// When the event occured.
pub timestamp: NaiveDateTime,
+ /// What occured.
pub kind: MumbleEventKind
}
@@ -20,11 +25,17 @@ impl fmt::Display for MumbleEvent {
/// The different kinds of events that can happen.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MumbleEventKind {
+ /// A user connected to the server and joined our channel. Contains `(user, channel)`.
UserConnected(String, String),
+ /// A user disconnected from the server while in our channel. Contains `(user, channel)`.
UserDisconnected(String, String),
+ /// A user {un,}{muted,deafened}. Contains a rendered message with what changed and the user.
UserMuteStateChanged(String), // This logic is kinda weird so we only store the rendered message.
+ /// A text message was received. Contains who sent the message.
TextMessageReceived(String),
+ /// A user switched to our channel from some other channel. Contains `(user, previous-channel)`.
UserJoinedChannel(String, String),
+ /// A user switched from our channel to some other channel. Contains `(user, new-channel)`.
UserLeftChannel(String, String),
}
@@ -56,73 +67,124 @@ impl fmt::Display for MumbleEventKind {
}
}
+/// Sent by a controller to mumd who might respond with a [CommandResponse]. Not
+/// all commands receive a response.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum Command {
+ /// No response.
ChannelJoin {
channel_identifier: String,
},
+
+ /// Response: [CommandResponse::ChannelList].
ChannelList,
+
+ /// Force reloading of config file from disk. No response.
ConfigReload,
+ /// Response: [CommandResponse::DeafenStatus]. Toggles if None.
DeafenSelf(Option<bool>),
Events {
block: bool
},
+ /// Set the outgoing audio volume (i.e. from you to the server). No response.
InputVolumeSet(f32),
+
+ /// Response: [CommandResponse::MuteStatus]. Toggles mute state if None.
MuteOther(String, Option<bool>),
+
+ /// Response: [CommandResponse::MuteStatus]. Toggles mute state if None.
MuteSelf(Option<bool>),
+
+ /// Set the master incoming audio volume (i.e. from the server to you).
+ /// No response.
OutputVolumeSet(f32),
+
+ /// Request a list of past messages. Blocks while waiting for more messages
+ /// if block is true. Response: multiple [CommandResponse::PastMessage].
PastMessages {
block: bool,
},
+
+ /// Response: [CommandResponse::Pong]. Used to test existance of a
+ /// mumd-instance.
Ping,
+
+ /// Send a message to some [MessageTarget].
SendMessage {
+ /// The message to send.
message: String,
+ /// The target(s) to send the message to.
targets: MessageTarget,
},
+
+ /// Connect to the specified server. Response: [CommandResponse::ServerConnect].
ServerConnect {
+ /// The URL or IP-adress to connect to.
host: String,
+ /// The port to connect to.
port: u16,
+ /// The username to connect with.
username: String,
+ /// The server password, if applicable. Not sent if None.
password: Option<String>,
+ /// Whether to accept an invalid server certificate or not.
accept_invalid_cert: bool,
},
+
+ /// Disconnect from the currently connected server. No response.
ServerDisconnect,
+
+ /// Send a server status request via UDP (e.g. not requiring a TCP connection).
+ /// Response: [CommandResponse::ServerStatus].
ServerStatus {
host: String,
port: u16,
},
+
+ /// Request the status of the current server. Response: [CommandResponse::Status].
Status,
+
+ /// The the volume of the specified user. No response.
UserVolumeSet(String, f32),
}
+/// A response to a sent [Command].
#[derive(Debug, Deserialize, Serialize)]
pub enum CommandResponse {
ChannelList {
channels: Channel,
},
+
DeafenStatus {
is_deafened: bool,
},
+
Event {
event: MumbleEvent,
},
+
MuteStatus {
is_muted: bool,
},
+
PastMessage {
message: (NaiveDateTime, String, String),
},
+
Pong,
+
ServerConnect {
welcome_message: Option<String>,
server_state: Server,
},
+
ServerStatus {
version: u32,
users: u32,
max_users: u32,
bandwidth: u32,
},
+
Status {
server_state: Server,
},
diff --git a/mumlib/src/config.rs b/mumlib/src/config.rs
index 3edef37..932e013 100644
--- a/mumlib/src/config.rs
+++ b/mumlib/src/config.rs
@@ -1,3 +1,5 @@
+//! Representations of the mumdrc configuration file.
+
use crate::error::ConfigError;
use crate::DEFAULT_PORT;
@@ -23,9 +25,13 @@ struct TOMLConfig {
servers: Option<Array>,
}
+/// Our representation of the mumdrc config file.
+// Deserialized via [TOMLConfig].
#[derive(Clone, Debug, Default)]
pub struct Config {
+ /// General audio configuration.
pub audio: AudioConfig,
+ /// Saved servers.
pub servers: Vec<ServerConfig>,
/// Whether we allow connecting to servers with invalid server certificates.
///
@@ -34,6 +40,15 @@ pub struct Config {
}
impl Config {
+ /// Writes this config to the specified path.
+ ///
+ /// Pass create = true if you want the file to be created if it doesn't already exist.
+ ///
+ /// # Errors
+ ///
+ /// - [ConfigError::WontCreateFile] if the file doesn't exist and create = false was passed.
+ /// - Any [ConfigError::TOMLErrorSer] encountered when serializing the config.
+ /// - Any [ConfigError::IOError] encountered when writing the file.
pub fn write(&self, path: &Path, create: bool) -> Result<(), ConfigError> {
// Possible race here. It's fine since it shows when:
// 1) the file doesn't exist when checked and is then created
@@ -55,41 +70,60 @@ impl Config {
}
}
+/// Overwrite a specific sound effect with a file that should be played instead.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct SoundEffect {
+ /// During which event the effect should be played.
pub event: String,
+ /// The file that should be played.
pub file: String,
}
+/// General audio configuration.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct AudioConfig {
+ /// The microphone input sensitivity.
pub input_volume: Option<f32>,
+ /// The output main gain.
pub output_volume: Option<f32>,
+ /// Overriden sound effects.
pub sound_effects: Option<Vec<SoundEffect>>,
}
+/// A saved server.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ServerConfig {
+ /// The alias of the server.
pub name: String,
+ /// The host (URL or IP-adress) of the server.
pub host: String,
+ /// The port, if non-default.
pub port: Option<u16>,
+ /// The username to connect with. Prompted on connection if omitted.
pub username: Option<String>,
+ /// The password to connect with. Nothing is sent to the server if omitted.
pub password: Option<String>,
+ /// Whether to accept invalid server certifications for this server.
pub accept_invalid_cert: Option<bool>,
}
impl ServerConfig {
+ /// Creates a [SocketAddr] for this server.
+ ///
+ /// Returns `None` if no resolution could be made. See
+ /// [std::net::ToSocketAddrs] for more information.
pub fn to_socket_addr(&self) -> Option<SocketAddr> {
- match (self.host.as_str(), self.port.unwrap_or(DEFAULT_PORT))
- .to_socket_addrs()
- .map(|mut e| e.next())
- {
- Ok(Some(addr)) => Some(addr),
- _ => None,
- }
+ Some((self.host.as_str(), self.port.unwrap_or(DEFAULT_PORT))
+ .to_socket_addrs()
+ .ok()?
+ .next()?)
}
}
+/// Finds the default path of the configuration file.
+///
+/// The user config dir is looked for first (cross-platform friendly) and
+/// `/etc/mumdrc` is used as a fallback.
pub fn default_cfg_path() -> PathBuf {
match dirs::config_dir() {
Some(mut p) => {
@@ -143,6 +177,14 @@ impl From<Config> for TOMLConfig {
}
}
+/// Reads the config at the specified path.
+///
+/// If the file isn't found, returns a default config.
+///
+/// # Errors
+///
+/// - Any [ConfigError::TOMLErrorDe] encountered when deserializing the config.
+/// - Any [ConfigError::IOError] encountered when reading the file.
pub fn read_cfg(path: &Path) -> Result<Config, ConfigError> {
match fs::read_to_string(path) {
Ok(s) => {
diff --git a/mumlib/src/lib.rs b/mumlib/src/lib.rs
index 9b7d686..679db8d 100644
--- a/mumlib/src/lib.rs
+++ b/mumlib/src/lib.rs
@@ -1,3 +1,7 @@
+//! Shared items for crates that want to communicate with mumd and/or mumctl.
+
+// #![warn(missing_docs)]
+
pub mod command;
pub mod config;
pub mod error;
@@ -8,9 +12,15 @@ pub use error::Error;
use colored::*;
use log::*;
+/// The default file path to use for the socket.
pub const SOCKET_PATH: &str = "/tmp/mumd";
+
+/// The default mumble port.
pub const DEFAULT_PORT: u16 = 64738;
+/// Setup a minimal fern logger.
+///
+/// Format: `LEVEL [yyyy-mm-dd][HH:MM:SS] FILE:LINE MESSAGE`
pub fn setup_logger<T: Into<fern::Output>>(target: T, color: bool) {
fern::Dispatch::new()
.format(move |out, message, record| {
diff --git a/mumlib/src/state.rs b/mumlib/src/state.rs
index 6fad332..72c01a6 100644
--- a/mumlib/src/state.rs
+++ b/mumlib/src/state.rs
@@ -1,25 +1,58 @@
use serde::{Deserialize, Serialize};
use std::fmt;
+/// The state of the currently connected Mumble server.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Server {
+ /// State of the currently connected channel.
pub channels: Channel,
+ /// The welcome text we received when we connected.
pub welcome_text: Option<String>,
+ /// Our username.
pub username: String,
+ /// The host (ip:port) of the server.
pub host: String,
}
+/// A representation of a channel in a Mumble server.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Channel {
+ /// The description of the channel, if set.
pub description: Option<String>,
- pub links: Vec<Vec<usize>>, //to represent several walks through the tree to find channels its linked to
+ /// The maximum number of allowed users in this channel.
pub max_users: u32,
+ /// The name of this channel.
pub name: String,
+ /// Any children this channel has.
pub children: Vec<Channel>,
+ /// This channel's connected users.
pub users: Vec<User>,
+
+ links: Vec<Vec<usize>>, //to represent several walks through the tree to find channels its linked to
}
impl Channel {
+ /// Create a new Channel representation.
+ pub fn new(
+ name: String,
+ description: Option<String>,
+ max_users: u32,
+ ) -> Self {
+ Self {
+ description,
+ max_users,
+ name,
+ children: Vec::new(),
+ users: Vec::new(),
+
+ links: Vec::new(),
+ }
+ }
+
+ /// Create an iterator over this channel and its children in a [pre-order
+ /// traversal](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR)
+ /// which ensures that parent channels are returned before any of its
+ /// children.
pub fn iter(&self) -> Iter<'_> {
Iter {
me: Some(&self),
@@ -32,6 +65,8 @@ impl Channel {
}
}
+ /// Create an iterator over this channel and its childrens connected users
+ /// in a pre-order traversal.
pub fn users_iter(&self) -> UsersIter<'_> {
UsersIter {
channels: self.children.iter().map(|e| e.users_iter()).collect(),
@@ -46,6 +81,7 @@ impl Channel {
}
}
+/// An iterator over channels. Created by [Channel::iter].
pub struct Iter<'a> {
me: Option<&'a Channel>,
channel: Option<usize>,
@@ -76,6 +112,7 @@ impl<'a> Iterator for Iter<'a> {
}
}
+/// An iterator over users. Created by [Channel::users_iter].
pub struct UsersIter<'a> {
channel: Option<usize>,
channels: Vec<UsersIter<'a>>,