From 89944f0f56935dfd4c3adca6ff8f1fd52212ee03 Mon Sep 17 00:00:00 2001 From: Eskil Queseth Date: Fri, 13 Nov 2020 23:37:54 +0100 Subject: add mvp for playing sound when stuff happens --- mumd/Cargo.toml | 2 ++ mumd/src/audio.rs | 63 ++++++++++++++++++++++++++++++++++++++++++++++-- mumd/src/audio/output.rs | 14 ++++++++--- mumd/src/state.rs | 4 +-- 4 files changed, 75 insertions(+), 8 deletions(-) (limited to 'mumd') diff --git a/mumd/Cargo.toml b/mumd/Cargo.toml index 3dfc82c..a7a5ef8 100644 --- a/mumd/Cargo.toml +++ b/mumd/Cargo.toml @@ -29,6 +29,8 @@ serde = { version = "1.0", features = ["derive"] } tokio = { version = "0.2", features = ["full"] } tokio-tls = "0.3" tokio-util = { version = "0.3", features = ["codec", "udp"] } +hound = "3.4.0" +samplerate = "0.2.2" libnotify = { version = "1.0", optional = true } diff --git a/mumd/src/audio.rs b/mumd/src/audio.rs index 05e3ff5..65da656 100644 --- a/mumd/src/audio.rs +++ b/mumd/src/audio.rs @@ -7,9 +7,25 @@ use log::*; use mumble_protocol::voice::VoicePacketPayload; use opus::Channels; use std::collections::hash_map::Entry; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::sync::{Arc, Mutex}; use tokio::sync::{mpsc, watch}; +use samplerate::ConverterType; +use crate::audio::output::SaturatingAdd; + +//TODO? move to mumlib +pub const EVENT_SOUNDS: &[(&str, NotificationEvents)] = &[ + ("resources/connect.wav", NotificationEvents::ServerConnect), + ("resources/disconnect.wav", NotificationEvents::ServerDisconnect), +]; + +const SAMPLE_RATE: u32 = 48000; + +#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] +pub enum NotificationEvents { + ServerConnect, + ServerDisconnect, +} pub struct Audio { output_config: StreamConfig, @@ -24,11 +40,15 @@ pub struct Audio { user_volumes: Arc>>, client_streams: Arc>>, + + sounds: HashMap>, + + play_sounds: Arc>>, } impl Audio { pub fn new(input_volume: f32, output_volume: f32) -> Self { - let sample_rate = SampleRate(48000); + let sample_rate = SampleRate(SAMPLE_RATE); let host = cpal::default_host(); let output_device = host @@ -71,12 +91,14 @@ impl Audio { let user_volumes = Arc::new(Mutex::new(HashMap::new())); let (output_volume_sender, output_volume_receiver) = watch::channel::(output_volume); + let play_sounds = Arc::new(Mutex::new(VecDeque::new())); 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::( + Arc::clone(&play_sounds), Arc::clone(&client_streams), output_volume_receiver, Arc::clone(&user_volumes), @@ -86,6 +108,7 @@ impl Audio { SampleFormat::I16 => output_device.build_output_stream( &output_config, output::curry_callback::( + Arc::clone(&play_sounds), Arc::clone(&client_streams), output_volume_receiver, Arc::clone(&user_volumes), @@ -95,6 +118,7 @@ impl Audio { SampleFormat::U16 => output_device.build_output_stream( &output_config, output::curry_callback::( + Arc::clone(&play_sounds), Arc::clone(&client_streams), output_volume_receiver, Arc::clone(&user_volumes), @@ -160,6 +184,26 @@ impl Audio { output_stream.play().unwrap(); + let sounds = EVENT_SOUNDS.iter() + .map(|(path, event)| { + let reader = hound::WavReader::open(path).unwrap(); + let spec = reader.spec(); + debug!("{:?}", spec); + let samples = match spec.sample_format { + hound::SampleFormat::Float => reader.into_samples::().map(|e| e.unwrap()).collect::>(), + hound::SampleFormat::Int => reader.into_samples::().map(|e| cpal::Sample::to_f32(&e.unwrap())).collect::>(), + }; + let samples = samplerate::convert( + spec.sample_rate, + SAMPLE_RATE, + spec.channels as usize, + ConverterType::SincBestQuality, + &samples) + .unwrap(); + (*event, samples) + }) + .collect(); + Self { output_config, _output_stream: output_stream, @@ -167,8 +211,10 @@ impl Audio { input_volume_sender, input_channel_receiver: Some(input_receiver), client_streams, + sounds, output_volume_sender, user_volumes, + play_sounds, } } @@ -250,4 +296,17 @@ impl Audio { } } } + + pub fn play_effect(&mut self, effect: NotificationEvents) { + let samples = self.sounds.get(&effect).unwrap(); + + let mut play_sounds = self.play_sounds.lock().unwrap(); + + for (val, e) in play_sounds.iter_mut().zip(samples.iter()) { + *val = val.saturating_add(*e); + } + + let l = play_sounds.len(); + play_sounds.extend(samples.iter().skip(l)); + } } diff --git a/mumd/src/audio/output.rs b/mumd/src/audio/output.rs index ce116a8..2b58d5b 100644 --- a/mumd/src/audio/output.rs +++ b/mumd/src/audio/output.rs @@ -71,8 +71,9 @@ impl SaturatingAdd for u16 { } } -pub fn curry_callback( - buf: Arc>>, +pub fn curry_callback( + effect_sound: Arc>>, + user_bufs: Arc>>, output_volume_receiver: watch::Receiver, user_volumes: Arc>>, ) -> impl FnMut(&mut [T], &OutputCallbackInfo) + Send + 'static { @@ -83,8 +84,9 @@ pub fn curry_callback( let volume = *output_volume_receiver.borrow(); - let mut lock = buf.lock().unwrap(); - for (id, client_stream) in &mut *lock { + let mut effects_sound = effect_sound.lock().unwrap(); + let mut user_bufs = user_bufs.lock().unwrap(); + for (id, client_stream) in &mut *user_bufs { let (user_volume, muted) = user_volumes .lock() .unwrap() @@ -98,5 +100,9 @@ pub fn curry_callback( } } } + + for sample in data.iter_mut() { + *sample = sample.saturating_add(Sample::from(&(effects_sound.pop_front().unwrap_or(0.0) * volume))); + } } } diff --git a/mumd/src/state.rs b/mumd/src/state.rs index 8fe5e36..2c90b7c 100644 --- a/mumd/src/state.rs +++ b/mumd/src/state.rs @@ -2,7 +2,7 @@ pub mod channel; pub mod server; pub mod user; -use crate::audio::Audio; +use crate::audio::{Audio, NotificationEvents}; use crate::network::ConnectionInfo; use crate::notify; use crate::state::server::Server; @@ -85,7 +85,6 @@ impl State { state } - //TODO? move bool inside Result pub fn handle_command(&mut self, command: Command) -> ExecutionContext { match command { Command::ChannelJoin { channel_identifier } => { @@ -423,6 +422,7 @@ impl State { &msg.get_name(), channel.name() )); + self.audio.play_effect(NotificationEvents::ServerConnect); } } } -- cgit v1.2.1