Skip to main content

audiobox/sfx/
preset.rs

1use super::Effect;
2use crate::synth::{Duty, NoiseMode, Waveform};
3
4/// Kinds of effect that [`Effect::preset`] draws from a seed.
5#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
6pub enum Preset {
7    /// A coin or item: a short, bright tone, often with a jump up in pitch.
8    Pickup,
9    /// A shot: a fast downward slide.
10    Laser,
11    /// Filtered noise that falls and fades.
12    Explosion,
13    /// A rising tone, often repeated or with vibrato.
14    PowerUp,
15    /// A short, falling thud of noise or a harsh tone.
16    Hit,
17    /// A quick upward slide.
18    Jump,
19    /// A short beep for menus.
20    Blip,
21}
22impl Preset {
23    pub const ALL: [Preset; 7] = [
24        Self::Pickup,
25        Self::Laser,
26        Self::Explosion,
27        Self::PowerUp,
28        Self::Hit,
29        Self::Jump,
30        Self::Blip,
31    ];
32}
33
34/// SplitMix64: a fixed, portable generator, so a seed gives the same effect everywhere.
35struct Rng(u64);
36impl Rng {
37    fn next(&mut self) -> u64 {
38        self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15);
39        let mut z = self.0;
40        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
41        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
42        z ^ (z >> 31)
43    }
44    fn unit(&mut self) -> f64 {
45        (self.next() >> 11) as f64 / (1u64 << 53) as f64
46    }
47    /// A value in `low..=high`, rounded to three decimals.
48    fn range(&mut self, low: f64, high: f64) -> f64 {
49        ((low + (high - low) * self.unit()) * 1000.0).round() / 1000.0
50    }
51    fn chance(&mut self, probability: f64) -> bool {
52        self.unit() < probability
53    }
54    fn pick<T: Copy>(&mut self, items: &[T]) -> T {
55        items[(self.next() % items.len() as u64) as usize]
56    }
57}
58
59pub(super) fn generate(kind: Preset, seed: u64) -> Effect {
60    let index = Preset::ALL.iter().position(|&k| k == kind).unwrap_or(0) as u64;
61    let mut r = Rng(seed ^ index.wrapping_mul(0xd6e8_feb8_6659_fd93));
62    let duties = [Duty::Eighth, Duty::Quarter, Duty::Half];
63    let mut e = Effect::default();
64    match kind {
65        Preset::Pickup => {
66            e.waveform = if r.chance(0.75) {
67                Waveform::Pulse(r.pick(&duties))
68            } else {
69                Waveform::Sawtooth
70            };
71            e.frequency = r.range(700.0, 1700.0);
72            e.sustain = r.range(0.02, 0.1);
73            e.punch = r.range(0.3, 0.7);
74            e.decay = r.range(0.1, 0.35);
75            if r.chance(0.6) {
76                e.arpeggio = r.pick(&[3.0, 4.0, 5.0, 7.0, 12.0]);
77                e.arpeggio_time = r.range(0.03, 0.1);
78            }
79        }
80        Preset::Laser => {
81            let pulse = r.chance(0.6);
82            e.waveform = if pulse {
83                Waveform::Pulse(r.pick(&duties))
84            } else {
85                r.pick(&[Waveform::Sawtooth, Waveform::Triangle])
86            };
87            e.frequency = r.range(500.0, 2000.0);
88            e.slide = r.range(-8.0, -2.5);
89            e.slide_change = if r.chance(0.3) {
90                r.range(0.0, 20.0)
91            } else {
92                0.0
93            };
94            if pulse && r.chance(0.5) {
95                e.duty_sweep = r.range(-2.0, 2.0);
96            }
97            e.sustain = r.range(0.04, 0.15);
98            e.punch = if r.chance(0.5) {
99                r.range(0.0, 0.4)
100            } else {
101                0.0
102            };
103            e.decay = r.range(0.05, 0.2);
104            if r.chance(0.3) {
105                e.high_pass = r.range(100.0, 800.0);
106            }
107        }
108        Preset::Explosion => {
109            e.waveform = Waveform::Noise(NoiseMode::Long);
110            e.frequency = r.range(30.0, 250.0);
111            e.slide = r.range(-2.0, 0.0);
112            e.sustain = r.range(0.08, 0.3);
113            e.punch = r.range(0.2, 0.8);
114            e.decay = r.range(0.3, 0.9);
115            if r.chance(0.6) {
116                e.low_pass = r.range(800.0, 6000.0);
117                e.low_pass_sweep = r.range(-3.0, 0.0);
118            }
119            if r.chance(0.3) {
120                e.vibrato_depth = r.range(0.5, 3.0);
121                e.vibrato_speed = r.range(4.0, 14.0);
122            }
123            e.volume = 0.6;
124        }
125        Preset::PowerUp => {
126            e.waveform = if r.chance(0.6) {
127                Waveform::Pulse(r.pick(&duties))
128            } else {
129                Waveform::Sawtooth
130            };
131            e.frequency = r.range(250.0, 600.0);
132            e.slide = r.range(0.8, 3.0);
133            if r.chance(0.5) {
134                e.repeat = r.range(0.08, 0.2);
135            } else {
136                e.vibrato_depth = r.range(0.2, 1.0);
137                e.vibrato_speed = r.range(8.0, 20.0);
138            }
139            e.sustain = r.range(0.15, 0.4);
140            e.decay = r.range(0.1, 0.4);
141        }
142        Preset::Hit => {
143            let noise = r.chance(0.5);
144            e.waveform = if noise {
145                Waveform::Noise(NoiseMode::Long)
146            } else {
147                r.pick(&[Waveform::Sawtooth, Waveform::Pulse(Duty::Half)])
148            };
149            e.frequency = if noise {
150                r.range(40.0, 200.0)
151            } else {
152                r.range(100.0, 500.0)
153            };
154            e.slide = r.range(-6.0, -1.5);
155            e.sustain = r.range(0.01, 0.05);
156            e.punch = r.range(0.0, 0.5);
157            e.decay = r.range(0.05, 0.2);
158            if r.chance(0.5) {
159                e.high_pass = r.range(80.0, 500.0);
160            }
161        }
162        Preset::Jump => {
163            e.waveform = Waveform::Pulse(r.pick(&[Duty::Quarter, Duty::Half]));
164            e.frequency = r.range(250.0, 600.0);
165            e.slide = r.range(1.0, 3.0);
166            e.sustain = r.range(0.05, 0.15);
167            e.decay = r.range(0.1, 0.25);
168            if r.chance(0.4) {
169                e.low_pass = r.range(2000.0, 8000.0);
170            }
171            if r.chance(0.4) {
172                e.high_pass = r.range(100.0, 400.0);
173            }
174        }
175        Preset::Blip => {
176            e.waveform = if r.chance(0.7) {
177                Waveform::Pulse(r.pick(&[Duty::Quarter, Duty::Half]))
178            } else {
179                Waveform::Triangle
180            };
181            e.frequency = r.range(600.0, 1600.0);
182            e.sustain = r.range(0.02, 0.08);
183            e.decay = r.range(0.01, 0.05);
184            if r.chance(0.3) {
185                e.high_pass = r.range(100.0, 400.0);
186            }
187            e.volume = 0.4;
188        }
189    }
190    e
191}