Skip to main content

pfx_sound/
sound.rs

1use std::sync::Arc;
2
3use pfx_core::sim::splitmix64;
4
5use crate::channel::ChannelId;
6use crate::envelope::Envelope;
7use crate::interp::Resample;
8use crate::mix::Level;
9use crate::synth::Wave;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
12pub struct SoundId(pub(crate) usize);
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum Steal {
16    Oldest,
17    Quietest,
18}
19
20#[derive(Clone, Copy, Debug, PartialEq)]
21pub struct SoundSpec {
22    pub channel: ChannelId,
23    pub level: Level,
24    pub max_voices: usize,
25    pub steal: Steal,
26    pub pitch_jitter: f32,
27    pub volume_jitter: f32,
28    pub steal_fade: f32,
29    pub envelope: Option<Envelope>,
30    pub resample: Resample,
31}
32
33impl Default for SoundSpec {
34    fn default() -> Self {
35        Self {
36            channel: ChannelId::EFFECTS,
37            level: Level::default(),
38            max_voices: usize::MAX,
39            steal: Steal::Oldest,
40            pitch_jitter: 0.0,
41            volume_jitter: 0.0,
42            steal_fade: 0.003,
43            envelope: None,
44            resample: Resample::Cubic,
45        }
46    }
47}
48
49pub(crate) struct SoundDef {
50    pub(crate) body: Body,
51    pub(crate) spec: SoundSpec,
52}
53
54pub(crate) enum Body {
55    Clip {
56        samples: Arc<Vec<f32>>,
57        channels: u16,
58        frames: u64,
59    },
60    Tone {
61        wave: Wave,
62        hz: f32,
63        frames: Option<u64>,
64    },
65}
66
67pub(crate) struct Draw {
68    pub(crate) pitch: f32,
69    pub(crate) volume: f32,
70}
71
72pub(crate) fn draw(spec: &SoundSpec, rng: &mut Rng) -> Draw {
73    let pitch = pfx_core::sim::powf(2.0, rng.signed() * spec.pitch_jitter / 12.0);
74    let volume = (1.0 + rng.signed() * spec.volume_jitter).max(0.0);
75    Draw { pitch, volume }
76}
77
78pub(crate) struct Rng(u64);
79
80impl Rng {
81    pub(crate) fn new(seed: u64) -> Self {
82        Self(seed)
83    }
84
85    pub(crate) fn seed(&mut self) -> u64 {
86        self.next()
87    }
88
89    fn next(&mut self) -> u64 {
90        splitmix64(&mut self.0)
91    }
92
93    pub(crate) fn signed(&mut self) -> f32 {
94        let unit = (self.next() >> 40) as f32 / (1u64 << 24) as f32;
95        unit * 2.0 - 1.0
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn the_same_seed_gives_the_same_draws_and_they_stay_in_range() {
105        let mut a = Rng::new(7);
106        let mut b = Rng::new(7);
107        let mut c = Rng::new(8);
108        let drawn: Vec<f32> = (0..1000).map(|_| a.signed()).collect();
109        let again: Vec<f32> = (0..1000).map(|_| b.signed()).collect();
110        let other: Vec<f32> = (0..1000).map(|_| c.signed()).collect();
111        assert_eq!(drawn, again);
112        assert_ne!(drawn, other);
113        assert!(drawn.iter().all(|v| (-1.0..1.0).contains(v)));
114        let mean = drawn.iter().sum::<f32>() / 1000.0;
115        assert!(mean.abs() < 0.1, "{mean}");
116    }
117}