aboutsummaryrefslogtreecommitdiffstats
path: root/mumd/src/audio/sound_effects.rs
blob: 3d70bbb5432af8fc914d45877ee3fb7a690cc5de (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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 strum::IntoEnumIterator;
use strum_macros::EnumIter;

use crate::audio::SAMPLE_RATE;

#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, EnumIter)]
pub enum NotificationEvents {
    ServerConnect,
    ServerDisconnect,
    UserConnected,
    UserDisconnected,
    UserJoinedChannel,
    UserLeftChannel,
    Mute,
    Unmute,
    Deafen,
    Undeafen,
}

impl TryFrom<&str> for NotificationEvents {
    type Error = ();
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        match s {
            "server_connect" => Ok(NotificationEvents::ServerConnect),
            "server_disconnect" => Ok(NotificationEvents::ServerDisconnect),
            "user_connected" => Ok(NotificationEvents::UserConnected),
            "user_disconnected" => Ok(NotificationEvents::UserDisconnected),
            "user_joined_channel" => Ok(NotificationEvents::UserJoinedChannel),
            "user_left_channel" => Ok(NotificationEvents::UserLeftChannel),
            "mute" => Ok(NotificationEvents::Mute),
            "unmute" => Ok(NotificationEvents::Unmute),
            "deafen" => Ok(NotificationEvents::Deafen),
            "undeafen" => Ok(NotificationEvents::Undeafen),
            _ => {
                warn!("Unknown notification event '{}' in config", s);
                Err(())
            }
        }
    }
}

pub fn load_sound_effects(overrides: &[SoundEffect], num_channels: usize) -> HashMap<NotificationEvents, Vec<f32>> {
    let overrides: HashMap<_, _> = overrides
        .iter()
        .filter_map(|sound_effect| {
            let (event, file) = (&sound_effect.event, &sound_effect.file);
            if let Ok(event) = NotificationEvents::try_from(event.as_str()) {
                Some((event, file))
            } else {
                None
            }
        })
        .collect();

    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<_>>(),
            };
            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."),
            };
            let mut signal = signal::from_interleaved_samples_iter::<_, [f32; 2]>(iter);
            let interp = Linear::new(Signal::next(&mut signal), Signal::next(&mut signal));
            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
                .flat_map(|e| {
                    if num_channels == 1 {
                        vec![e[0]]
                    } else {
                        e.to_vec()
                    }
                })
                .collect::<Vec<f32>>();
            (event, samples)
        })
        .collect()
}

// moo
fn get_sfx(file: &str) -> Cow<'static, [u8]> {
    let mut buf: Vec<u8> = Vec::new();
    if let Ok(mut file) = File::open(file) {
        file.read_to_end(&mut buf).unwrap();
        Cow::from(buf)
    } else {
        warn!("File not found: '{}'", file);
        get_default_sfx()
    }
}

fn get_default_sfx() -> Cow<'static, [u8]> {
    Cow::from(include_bytes!("fallback_sfx.wav").as_ref())
}