aboutsummaryrefslogtreecommitdiffstats
path: root/mumd/src/audio
diff options
context:
space:
mode:
authorGustav Sörnäs <gustav@sornas.net>2021-06-19 14:22:55 +0200
committerGustav Sörnäs <gustav@sornas.net>2021-06-19 14:28:23 +0200
commit008025b9dc2c833d14d9bc2a3c1dbc5328d80a9e (patch)
tree4480a8aeea7a5a0270ee140aeb106ff22b0cbc0b /mumd/src/audio
parent311c19f92de6d421a40d7144127fc43bb5a01d40 (diff)
downloadmum-008025b9dc2c833d14d9bc2a3c1dbc5328d80a9e.tar.gz
refactor out wav unpacking
Diffstat (limited to 'mumd/src/audio')
-rw-r--r--mumd/src/audio/sound_effects.rs105
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())
}