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}