diff options
| author | Gustav Sörnäs <gustav@sornas.net> | 2021-06-19 14:22:55 +0200 |
|---|---|---|
| committer | Gustav Sörnäs <gustav@sornas.net> | 2021-06-19 14:28:23 +0200 |
| commit | 008025b9dc2c833d14d9bc2a3c1dbc5328d80a9e (patch) | |
| tree | 4480a8aeea7a5a0270ee140aeb106ff22b0cbc0b /mumd/src/audio/sound_effects.rs | |
| parent | 311c19f92de6d421a40d7144127fc43bb5a01d40 (diff) | |
| download | mum-008025b9dc2c833d14d9bc2a3c1dbc5328d80a9e.tar.gz | |
refactor out wav unpacking
Diffstat (limited to 'mumd/src/audio/sound_effects.rs')
| -rw-r--r-- | mumd/src/audio/sound_effects.rs | 105 |
1 files changed, 84 insertions, 21 deletions
diff --git a/mumd/src/audio/sound_effects.rs b/mumd/src/audio/sound_effects.rs index 3d70bbb..aac9852 100644 --- a/mumd/src/audio/sound_effects.rs +++ b/mumd/src/audio/sound_effects.rs @@ -2,12 +2,37 @@ use dasp_interpolate::linear::Linear; use dasp_signal::{self as signal, Signal}; use log::warn; use mumlib::config::SoundEffect; -use std::{borrow::Cow, collections::HashMap, convert::TryFrom, fs::File, io::Read}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fs::File; +use std::io::Read; +use std::path::Path; use strum::IntoEnumIterator; use strum_macros::EnumIter; use crate::audio::SAMPLE_RATE; +enum AudioFileKind { + Wav, +} + +impl TryFrom<&str> for AudioFileKind { + type Error = (); + + fn try_from(s: &str) -> Result<Self, Self::Error> { + match s { + ".wav" => Ok(AudioFileKind::Wav), + _ => Err(()), + } + } +} + +struct AudioSpec { + channels: u32, + sample_rate: u32, +} + #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, EnumIter)] pub enum NotificationEvents { ServerConnect, @@ -24,6 +49,7 @@ pub enum NotificationEvents { impl TryFrom<&str> for NotificationEvents { type Error = (); + fn try_from(s: &str) -> Result<Self, Self::Error> { match s { "server_connect" => Ok(NotificationEvents::ServerConnect), @@ -57,35 +83,41 @@ pub fn load_sound_effects(overrides: &[SoundEffect], num_channels: usize) -> Has }) .collect(); + // Construct a hashmap that maps every [NotificationEvent] to a vector of + // plain floating point audio data with the global sample rate as a + // Vec<f32>. We do this by iterating over all [NotificationEvent]-variants + // and opening either the file passed as an override or the fallback sound + // effect (if omitted). We then use dasp to convert to the correct sample rate. NotificationEvents::iter() .map(|event| { - let bytes = overrides - .get(&event) - .map(|file| get_sfx(file)) - .unwrap_or_else(get_default_sfx); - let reader = hound::WavReader::new(bytes.as_ref()).unwrap(); - let spec = reader.spec(); - let samples = match spec.sample_format { - hound::SampleFormat::Float => reader - .into_samples::<f32>() - .map(|e| e.unwrap()) - .collect::<Vec<_>>(), - hound::SampleFormat::Int => reader - .into_samples::<i16>() - .map(|e| cpal::Sample::to_f32(&e.unwrap())) - .collect::<Vec<_>>(), - }; + // Open the file if overriden, otherwise use the default sound effect. + let file = overrides.get(&event); + let (data, kind) = file + .map(|file| ( + get_sfx(file), + AudioFileKind::Wav + )) + .unwrap_or_else(|| (get_default_sfx(), AudioFileKind::Wav)); + // Unpack the samples. + let (samples, spec) = unpack_audio(data, kind); + // If the audio is mono (single channel), pad every sample with + // itself, since we later assume that audio is stored as LRLRLR (or + // RLRLRL). Without this, mono audio would be played in double + // speed. let iter: Box<dyn Iterator<Item = f32>> = match spec.channels { 1 => Box::new(samples.into_iter().flat_map(|e| vec![e, e])), 2 => Box::new(samples.into_iter()), _ => unimplemented!("Only mono and stereo sound is supported. See #80."), }; + // Create a dasp signal containing stereo sound. let mut signal = signal::from_interleaved_samples_iter::<_, [f32; 2]>(iter); + // Create a linear interpolator, in case we need to convert the sample rate. let interp = Linear::new(Signal::next(&mut signal), Signal::next(&mut signal)); + // Create our resulting samples. let samples = signal .from_hz_to_hz(interp, spec.sample_rate as f64, SAMPLE_RATE as f64) .until_exhausted() - // if the source audio is stereo and is being played as mono, discard the right audio + // If the source audio is stereo and is being played as mono, discard the first channel. .flat_map(|e| { if num_channels == 1 { vec![e[0]] @@ -99,18 +131,49 @@ pub fn load_sound_effects(overrides: &[SoundEffect], num_channels: usize) -> Has .collect() } +/// Unpack audio data. The required audio spec is read from the file and returned as well. +fn unpack_audio(data: Cow<[u8]>, kind: AudioFileKind) -> (Vec<f32>, AudioSpec) { + match kind { + AudioFileKind::Wav => unpack_wav(data), + } +} + +/// Unpack wav data. +fn unpack_wav(data: Cow<[u8]>) -> (Vec<f32>, AudioSpec) { + let reader = hound::WavReader::new(data.as_ref()).unwrap(); + let spec = reader.spec(); + let samples = match spec.sample_format { + hound::SampleFormat::Float => reader + .into_samples::<f32>() + .map(|e| e.unwrap()) + .collect::<Vec<_>>(), + hound::SampleFormat::Int => reader + .into_samples::<i16>() + .map(|e| cpal::Sample::to_f32(&e.unwrap())) + .collect::<Vec<_>>(), + }; + let spec = AudioSpec { + channels: spec.channels as u32, + sample_rate: spec.sample_rate, + }; + (samples, spec) +} + +/// Open and return the data contained in a file, or the default sound effect if +/// the file couldn't be found. // moo -fn get_sfx(file: &str) -> Cow<'static, [u8]> { +fn get_sfx<P: AsRef<Path>>(file: P) -> Cow<'static, [u8]> { let mut buf: Vec<u8> = Vec::new(); - if let Ok(mut file) = File::open(file) { + if let Ok(mut file) = File::open(file.as_ref()) { file.read_to_end(&mut buf).unwrap(); Cow::from(buf) } else { - warn!("File not found: '{}'", file); + warn!("File not found: '{}'", file.as_ref().display()); get_default_sfx() } } +/// Get the default sound effect. fn get_default_sfx() -> Cow<'static, [u8]> { Cow::from(include_bytes!("fallback_sfx.wav").as_ref()) } |
