diff options
| author | Eskil <eskilq@kth.se> | 2020-11-03 22:02:32 +0100 |
|---|---|---|
| committer | Eskil <eskilq@kth.se> | 2020-11-03 22:02:32 +0100 |
| commit | d6496cb0f6abba855b04338fa8bc5aaa89487c29 (patch) | |
| tree | 929f5c18babe5bc27676c88aae6b9d3795b09917 | |
| parent | 831182b69eb1bbfedfad1288b73a822241f18d25 (diff) | |
| parent | 972c11fe66c17728981ec57796c78fb70c7bd180 (diff) | |
| download | mum-d6496cb0f6abba855b04338fa8bc5aaa89487c29.tar.gz | |
Merge branch 'audio-volume' into 'main'
Refactor of config and add support for changing global output volume and individual user's volume
See merge request gustav/mum!28
| -rw-r--r-- | mumctl/src/main.rs | 344 | ||||
| -rw-r--r-- | mumd/src/audio.rs | 41 | ||||
| -rw-r--r-- | mumd/src/audio/output.rs | 10 | ||||
| -rw-r--r-- | mumd/src/state.rs | 45 | ||||
| -rw-r--r-- | mumlib/src/command.rs | 2 | ||||
| -rw-r--r-- | mumlib/src/config.rs | 47 | ||||
| -rw-r--r-- | mumlib/src/error.rs | 2 | ||||
| -rw-r--r-- | usage.org | 6 |
8 files changed, 268 insertions, 229 deletions
diff --git a/mumctl/src/main.rs b/mumctl/src/main.rs index 9d38aa0..11c51e6 100644 --- a/mumctl/src/main.rs +++ b/mumctl/src/main.rs @@ -100,6 +100,16 @@ fn main() { .arg(Arg::with_name("zsh").long("zsh")) .arg(Arg::with_name("bash").long("bash")) .arg(Arg::with_name("fish").long("fish")), + ) + .subcommand( + SubCommand::with_name("volume") + .subcommand( + SubCommand::with_name("set") + .arg(Arg::with_name("user").required(true)) + .arg(Arg::with_name("volume").required(true)), + ) + .arg(Arg::with_name("user").required(true)) + .setting(AppSettings::SubcommandsNegateReqs), ); let matches = app.clone().get_matches(); @@ -180,6 +190,13 @@ fn main() { "audio.input_volume" => { if let Ok(volume) = value.parse() { send_command(Command::InputVolumeSet(volume)).unwrap(); + config.audio.input_volume = Some(volume); + } + } + "audio.output_volume" => { + if let Ok(volume) = value.parse() { + send_command(Command::OutputVolumeSet(volume)).unwrap(); + config.audio.output_volume = Some(volume); } } _ => { @@ -199,29 +216,41 @@ fn main() { &mut io::stdout(), ); return; + } else if let Some(matches) = matches.subcommand_matches("volume") { + if let Some(matches) = matches.subcommand_matches("set") { + let user = matches.value_of("user").unwrap(); + let volume = matches.value_of("volume").unwrap(); + if let Ok(val) = volume.parse() { + err_print!(send_command(Command::UserVolumeSet(user.to_string(), val))) + } else { + println!("{} Invalid volume value: {}", "error:".red(), volume); + } + } else { + let _user = matches.value_of("user").unwrap(); + //TODO implement me + //needs work on mumd to implement + } }; - if let Some(config) = config { - if !config::cfg_exists() { - println!( - "Config file not found. Create one in {}? [Y/n]", - config::get_creatable_cfg_path() - ); - let stdin = std::io::stdin(); - let response = stdin.lock().lines().next(); - match response.map(|e| e.map(|e| &e == "Y")) { - Some(Ok(true)) => { - config.write_default_cfg(true).unwrap(); - } - _ => {} + if !config::cfg_exists() { + println!( + "Config file not found. Create one in {}? [Y/n]", + config::get_creatable_cfg_path() + ); + let stdin = std::io::stdin(); + let response = stdin.lock().lines().next(); + match response.map(|e| e.map(|e| &e == "Y")) { + Some(Ok(true)) => { + config.write_default_cfg(true).unwrap(); } - } else { - config.write_default_cfg(false).unwrap(); + _ => {} } + } else { + config.write_default_cfg(false).unwrap(); } } -fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &Option<mumlib::config::Config>) { +fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &mumlib::config::Config) { let host = matches.value_of("host").unwrap(); let username = matches.value_of("username"); let port = match matches.value_of("port").map(|e| e.parse()) { @@ -230,41 +259,36 @@ fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &Option<mumlib:: Some(Ok(v)) => Some(v), }; if let Some(port) = port { - let response = match config.as_ref().and_then(|e| { - e.servers - .as_ref() - .and_then(|e| e.iter().find(|e| e.name == host)) - }) { - Some(config) => { - let host = config.host.as_str(); - let port = config.port.unwrap_or(port); - let username = config.username.as_ref().map(|e| e.as_str()).or(username); + let (host, port, username) = match config.servers.iter().find(|e| e.name == host) { + Some(server_config) => { + let host = server_config.host.as_str(); + let port = server_config.port.unwrap_or(port); + let username = server_config + .username + .as_ref() + .map(|e| e.as_str()) + .or(username); if username.is_none() { println!("{} no username specified", "error:".red()); return; } - send_command(Command::ServerConnect { - host: host.to_string(), - port, - username: username.unwrap().to_string(), - accept_invalid_cert: true, //TODO - }) - .map(|e| (e, host)) + (host, port, username.unwrap()) } None => { if username.is_none() { println!("{} no username specified", "error:".red()); return; } - send_command(Command::ServerConnect { - host: host.to_string(), - port, - username: username.unwrap().to_string(), - accept_invalid_cert: true, //TODO - }) - .map(|e| (e, host)) + (host, port, username.unwrap()) } }; + let response = send_command(Command::ServerConnect { + host: host.to_string(), + port, + username: username.to_string(), + accept_invalid_cert: true, //TODO + }) + .map(|e| (e, host)); match response { Ok((e, host)) => { if let Some(CommandResponse::ServerConnect { welcome_message }) = e { @@ -281,197 +305,143 @@ fn match_server_connect(matches: &clap::ArgMatches<'_>, config: &Option<mumlib:: } } -fn match_server_config( - matches: &clap::ArgMatches<'_>, - config: &mut Option<mumlib::config::Config>, -) { - if config.is_none() { - *config = Some(mumlib::config::Config::default()); - } - - let config = config.as_mut().unwrap(); - +fn match_server_config(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { if let Some(server_name) = matches.value_of("server_name") { - if let Some(servers) = &mut config.servers { - let server = servers.iter_mut().find(|s| s.name == server_name); - if let Some(server) = server { - if let Some(var_name) = matches.value_of("var_name") { - if let Some(var_value) = matches.value_of("var_value") { - // save var_value in var_name (if it is valid) + let server = config.servers.iter_mut().find(|s| s.name == server_name); + if let Some(server) = server { + if let Some(var_name) = matches.value_of("var_name") { + if let Some(var_value) = matches.value_of("var_value") { + // save var_value in var_name (if it is valid) + match var_name { + "name" => { + println!("{} use mumctl server rename instead!", "error:".red()); + } + "host" => { + server.host = var_value.to_string(); + } + "port" => { + server.port = Some(var_value.parse().unwrap()); + } + "username" => { + server.username = Some(var_value.to_string()); + } + "password" => { + server.password = Some(var_value.to_string()); //TODO ask stdin if empty + } + _ => { + println!("{} variable {} not found", "error:".red(), var_name); + } + }; + } else { + // var_value is None + // print value of var_name + println!( + "{}", match var_name { "name" => { - println!("{} use mumctl server rename instead!", "error:".red()); + server.name.to_string() } "host" => { - server.host = var_value.to_string(); + server.host.to_string() } "port" => { - server.port = Some(var_value.parse().unwrap()); + server + .port + .map(|s| s.to_string()) + .unwrap_or(format!("{} not set", "error:".red())) } "username" => { - server.username = Some(var_value.to_string()); + server + .username + .as_ref() + .map(|s| s.to_string()) + .unwrap_or(format!("{} not set", "error:".red())) } "password" => { - server.password = Some(var_value.to_string()); //TODO ask stdin if empty + server + .password + .as_ref() + .map(|s| s.to_string()) + .unwrap_or(format!("{} not set", "error:".red())) } _ => { - println!("{} variable {} not found", "error:".red(), var_name); - } - }; - } else { - // var_value is None - // print value of var_name - println!( - "{}", - match var_name { - "name" => { - server.name.to_string() - } - "host" => { - server.host.to_string() - } - "port" => { - server - .port - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - "username" => { - server - .username - .as_ref() - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - "password" => { - server - .password - .as_ref() - .map(|s| s.to_string()) - .unwrap_or(format!("{} not set", "error:".red())) - } - _ => { - format!("{} unknown variable", "error:".red()) - } + format!("{} unknown variable", "error:".red()) } - ); - } - } else { - // var_name is None - // print server config - print!( - "{}{}{}{}", - format!("host: {}\n", server.host.to_string()), - server - .port - .map(|s| format!("port: {}\n", s)) - .unwrap_or("".to_string()), - server - .username - .as_ref() - .map(|s| format!("username: {}\n", s)) - .unwrap_or("".to_string()), - server - .password - .as_ref() - .map(|s| format!("password: {}\n", s)) - .unwrap_or("".to_string()), - ) + } + ); } } else { - // server is None - println!("{} server {} not found", "error:".red(), server_name); + // var_name is None + // print server config + print!( + "{}{}{}{}", + format!("host: {}\n", server.host.to_string()), + server + .port + .map(|s| format!("port: {}\n", s)) + .unwrap_or("".to_string()), + server + .username + .as_ref() + .map(|s| format!("username: {}\n", s)) + .unwrap_or("".to_string()), + server + .password + .as_ref() + .map(|s| format!("password: {}\n", s)) + .unwrap_or("".to_string()), + ) } } else { - // servers is None - println!("{} no servers found in configuration", "error:".red()); + // server is None + println!("{} server {} not found", "error:".red(), server_name); } } else { - for server in config.servers.iter().flat_map(|e| e.iter()) { + for server in config.servers.iter() { println!("{}", server.name); } } } -fn match_server_rename( - matches: &clap::ArgMatches<'_>, - config: &mut Option<mumlib::config::Config>, -) { - if config.is_none() { - *config = Some(mumlib::config::Config::default()); - } - - let config = config.as_mut().unwrap(); - - if let Some(servers) = &mut config.servers { - let prev_name = matches.value_of("prev_name").unwrap(); - let next_name = matches.value_of("next_name").unwrap(); - if let Some(server) = servers.iter_mut().find(|s| s.name == prev_name) { - server.name = next_name.to_string(); - } else { - println!("{} server {} not found", "error:".red(), prev_name); - } +fn match_server_rename(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { + let prev_name = matches.value_of("prev_name").unwrap(); + let next_name = matches.value_of("next_name").unwrap(); + if let Some(server) = config.servers.iter_mut().find(|s| s.name == prev_name) { + server.name = next_name.to_string(); + } else { + println!("{} server {} not found", "error:".red(), prev_name); } } -fn match_server_remove( - matches: &clap::ArgMatches<'_>, - config: &mut Option<mumlib::config::Config>, -) { - if config.is_none() { - *config = Some(mumlib::config::Config::default()); - } - - let config = config.as_mut().unwrap(); - +fn match_server_remove(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { let name = matches.value_of("name").unwrap(); - if let Some(servers) = &mut config.servers { - match servers.iter().position(|server| server.name == name) { - Some(idx) => { - servers.remove(idx); - } - None => { - println!("{} server {} not found", "error:".red(), name); - } - }; - } else { - println!("{} no servers found in configuration", "error:".red()); - } + match config.servers.iter().position(|server| server.name == name) { + Some(idx) => { + config.servers.remove(idx); + } + None => { + println!("{} server {} not found", "error:".red(), name); + } + }; } -fn match_server_add(matches: &clap::ArgMatches<'_>, config: &mut Option<mumlib::config::Config>) { - if config.is_none() { - *config = Some(mumlib::config::Config::default()); - } - - let mut config = config.as_mut().unwrap(); - +fn match_server_add(matches: &clap::ArgMatches<'_>, config: &mut mumlib::config::Config) { let name = matches.value_of("name").unwrap().to_string(); let host = matches.value_of("host").unwrap().to_string(); // optional arguments map None to None let port = matches.value_of("port").map(|s| s.parse().unwrap()); let username = matches.value_of("username").map(|s| s.to_string()); let password = matches.value_of("password").map(|s| s.to_string()); - if let Some(servers) = &mut config.servers { - if servers.iter().any(|s| s.name == name) { - println!("{} a server named {} already exists", "error:".red(), name); - } else { - servers.push(ServerConfig { - name, - host, - port, - username, - password, - }); - } + if config.servers.iter().any(|s| s.name == name) { + println!("{} a server named {} already exists", "error:".red(), name); } else { - config.servers = Some(vec![ServerConfig { + config.servers.push(ServerConfig { name, host, port, username, password, - }]); + }); } } diff --git a/mumd/src/audio.rs b/mumd/src/audio.rs index ad4a762..9f837f7 100644 --- a/mumd/src/audio.rs +++ b/mumd/src/audio.rs @@ -19,6 +19,10 @@ pub struct Audio { input_channel_receiver: Option<mpsc::Receiver<VoicePacketPayload>>, input_volume_sender: watch::Sender<f32>, + output_volume_sender: watch::Sender<f32>, + + user_volumes: Arc<Mutex<HashMap<u32, f32>>>, + client_streams: Arc<Mutex<HashMap<u32, output::ClientStream>>>, } @@ -65,21 +69,36 @@ impl Audio { let err_fn = |err| error!("An error occurred on the output audio stream: {}", err); + let user_volumes = Arc::new(Mutex::new(HashMap::new())); + let (output_volume_sender, output_volume_receiver) = watch::channel::<f32>(1.0); + let client_streams = Arc::new(Mutex::new(HashMap::new())); let output_stream = match output_supported_sample_format { SampleFormat::F32 => output_device.build_output_stream( &output_config, - output::curry_callback::<f32>(Arc::clone(&client_streams)), + output::curry_callback::<f32>( + Arc::clone(&client_streams), + output_volume_receiver, + Arc::clone(&user_volumes), + ), err_fn, ), SampleFormat::I16 => output_device.build_output_stream( &output_config, - output::curry_callback::<i16>(Arc::clone(&client_streams)), + output::curry_callback::<i16>( + Arc::clone(&client_streams), + output_volume_receiver, + Arc::clone(&user_volumes), + ), err_fn, ), SampleFormat::U16 => output_device.build_output_stream( &output_config, - output::curry_callback::<u16>(Arc::clone(&client_streams)), + output::curry_callback::<u16>( + Arc::clone(&client_streams), + output_volume_receiver, + Arc::clone(&user_volumes), + ), err_fn, ), } @@ -109,7 +128,7 @@ impl Audio { input_encoder, input_sender, input_config.sample_rate.0, - input_volume_receiver.clone(), + input_volume_receiver, 4, // 10 ms ), err_fn, @@ -120,7 +139,7 @@ impl Audio { input_encoder, input_sender, input_config.sample_rate.0, - input_volume_receiver.clone(), + input_volume_receiver, 4, // 10 ms ), err_fn, @@ -131,7 +150,7 @@ impl Audio { input_encoder, input_sender, input_config.sample_rate.0, - input_volume_receiver.clone(), + input_volume_receiver, 4, // 10 ms ), err_fn, @@ -148,6 +167,8 @@ impl Audio { input_volume_sender, input_channel_receiver: Some(input_receiver), client_streams, + output_volume_sender, + user_volumes, } } @@ -203,4 +224,12 @@ impl Audio { pub fn set_input_volume(&self, input_volume: f32) { self.input_volume_sender.broadcast(input_volume).unwrap(); } + + pub fn set_output_volume(&self, output_volume: f32) { + self.output_volume_sender.broadcast(output_volume).unwrap(); + } + + pub fn set_user_volume(&self, id: u32, volume: f32) { + self.user_volumes.lock().unwrap().insert(id, volume); + } } diff --git a/mumd/src/audio/output.rs b/mumd/src/audio/output.rs index 94e4b21..56da596 100644 --- a/mumd/src/audio/output.rs +++ b/mumd/src/audio/output.rs @@ -4,6 +4,7 @@ use opus::Channels; use std::collections::{HashMap, VecDeque}; use std::ops::AddAssign; use std::sync::{Arc, Mutex}; +use tokio::sync::watch; pub struct ClientStream { buffer: VecDeque<f32>, //TODO ring buffer? @@ -72,17 +73,22 @@ impl SaturatingAdd for u16 { pub fn curry_callback<T: Sample + AddAssign + SaturatingAdd>( buf: Arc<Mutex<HashMap<u32, ClientStream>>>, + output_volume_receiver: watch::Receiver<f32>, + user_volumes: Arc<Mutex<HashMap<u32, f32>>>, ) -> impl FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static { move |data: &mut [T], _info: &OutputCallbackInfo| { for sample in data.iter_mut() { *sample = Sample::from(&0.0); } + let volume = *output_volume_receiver.borrow(); + let mut lock = buf.lock().unwrap(); - for client_stream in lock.values_mut() { + for (id, client_stream) in &mut *lock { + let user_volume = user_volumes.lock().unwrap().get(id).cloned().unwrap_or(1.0); for sample in data.iter_mut() { *sample = sample.saturating_add(Sample::from( - &client_stream.buffer.pop_front().unwrap_or(0.0), + &(client_stream.buffer.pop_front().unwrap_or(0.0) * volume * user_volume), )); } } diff --git a/mumd/src/state.rs b/mumd/src/state.rs index 306ded8..56ce030 100644 --- a/mumd/src/state.rs +++ b/mumd/src/state.rs @@ -53,7 +53,7 @@ pub enum StatePhase { } pub struct State { - config: Option<Config>, + config: Config, server: Option<Server>, audio: Audio, @@ -217,12 +217,35 @@ impl State { .unwrap(); now!(Ok(None)) } + Command::ConfigReload => { + self.reload_config(); + now!(Ok(None)) + } Command::InputVolumeSet(volume) => { self.audio.set_input_volume(volume); now!(Ok(None)) } - Command::ConfigReload => { - self.reload_config(); + Command::OutputVolumeSet(volume) => { + self.audio.set_output_volume(volume); + now!(Ok(None)) + } + Command::UserVolumeSet(string, volume) => { + if !matches!(*self.phase_receiver().borrow(), StatePhase::Connected) { + return now!(Err(Error::DisconnectedError)); + } + let user_id = match self + .server() + .unwrap() + .users() + .iter() + .find(|e| e.1.name() == &string) + .map(|e| *e.0) + { + None => return now!(Err(Error::InvalidUsernameError(string))), + Some(v) => v, + }; + + self.audio.set_user_volume(user_id, volume); now!(Ok(None)) } Command::ServerStatus { host, port } => ExecutionContext::Ping( @@ -379,16 +402,12 @@ impl State { } pub fn reload_config(&mut self) { - if let Some(config) = mumlib::config::read_default_cfg() { - self.config = Some(config); - let config = &self.config.as_ref().unwrap(); - if let Some(audio_config) = &config.audio { - if let Some(input_volume) = audio_config.input_volume { - self.audio.set_input_volume(input_volume); - } - } - } else { - warn!("config file not found"); + self.config = mumlib::config::read_default_cfg(); + if let Some(input_volume) = self.config.audio.input_volume { + self.audio.set_input_volume(input_volume); + } + if let Some(output_volume) = self.config.audio.output_volume { + self.audio.set_output_volume(output_volume); } } diff --git a/mumlib/src/command.rs b/mumlib/src/command.rs index 9b0c9ed..2fed4f1 100644 --- a/mumlib/src/command.rs +++ b/mumlib/src/command.rs @@ -10,6 +10,8 @@ pub enum Command { ChannelList, ConfigReload, InputVolumeSet(f32), + OutputVolumeSet(f32), + UserVolumeSet(String, f32), ServerConnect { host: String, port: u16, diff --git a/mumlib/src/config.rs b/mumlib/src/config.rs index ae569aa..3a2fa27 100644 --- a/mumlib/src/config.rs +++ b/mumlib/src/config.rs @@ -15,8 +15,8 @@ struct TOMLConfig { #[derive(Clone, Debug, Default)] pub struct Config { - pub audio: Option<AudioConfig>, - pub servers: Option<Vec<ServerConfig>>, + pub audio: AudioConfig, + pub servers: Vec<ServerConfig>, } impl Config { @@ -46,9 +46,10 @@ impl Config { } } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct AudioConfig { pub input_volume: Option<f32>, + pub output_volume: Option<f32>, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -127,7 +128,7 @@ impl TryFrom<TOMLConfig> for Config { fn try_from(config: TOMLConfig) -> Result<Self, Self::Error> { Ok(Config { - audio: config.audio, + audio: config.audio.unwrap_or_default(), servers: config .servers .map(|servers| { @@ -136,7 +137,8 @@ impl TryFrom<TOMLConfig> for Config { .map(|s| s.try_into::<ServerConfig>()) .collect() }) - .transpose()?, + .transpose()? + .unwrap_or(Vec::new()), }) } } @@ -144,26 +146,29 @@ impl TryFrom<TOMLConfig> for Config { impl From<Config> for TOMLConfig { fn from(config: Config) -> Self { TOMLConfig { - audio: config.audio, - servers: config.servers.map(|servers| { - servers + audio: if config.audio.output_volume.is_some() || config.audio.input_volume.is_some() { + Some(config.audio) + } else { + None + }, + servers: Some( + config + .servers .into_iter() .map(|s| Value::try_from::<ServerConfig>(s).unwrap()) - .collect() - }), + .collect(), + ), } } } -pub fn read_default_cfg() -> Option<Config> { - Some( - Config::try_from( - toml::from_str::<TOMLConfig>(&match fs::read_to_string(get_cfg_path()) { - Ok(f) => f.to_string(), - Err(_) => return None, - }) - .expect("invalid TOML in config file"), //TODO - ) - .expect("invalid config in TOML"), - ) //TODO +pub fn read_default_cfg() -> Config { + Config::try_from( + toml::from_str::<TOMLConfig>(&match fs::read_to_string(get_cfg_path()) { + Ok(f) => f, + Err(_) => return Config::default(), + }) + .expect("invalid TOML in config file"), //TODO + ) + .expect("invalid config in TOML") //TODO } diff --git a/mumlib/src/error.rs b/mumlib/src/error.rs index c6d9255..1e79b9c 100644 --- a/mumlib/src/error.rs +++ b/mumlib/src/error.rs @@ -10,6 +10,7 @@ pub enum Error { AlreadyConnectedError, ChannelIdentifierError(String, ChannelIdentifierError), InvalidServerAddrError(String, u16), + InvalidUsernameError(String), } impl Display for Error { @@ -21,6 +22,7 @@ impl Display for Error { Error::InvalidServerAddrError(addr, port) => { write!(f, "Invalid server address: {}: {}", addr, port) } + Error::InvalidUsernameError(username) => write!(f, "Invalid username: {}", username), } } } @@ -145,3 +145,9 @@ $ mumctl config audio.input_volume 1.1 $ mumctl config audio.input_volume $ mumctl config audio.input_volume --help #+END_SRC +** TODO volume +#+BEGIN_SRC bash +$ mumctl volume set User1 1.1 +$ mumctl volume User1 +110% +#+END_SRC |
