1use std::path::{Path, PathBuf};
17
18pub const NOISES: &[&str] = &["white", "pink", "brown"];
20pub const NATURE: &[&str] = &["waves", "rain", "wind", "crickets", "storm"];
22
23const RATE: u32 = 44_100;
24const SECONDS: usize = 60;
25const FADE: usize = 2 * RATE as usize;
27
28struct Random(u64);
30
31impl Random {
32 fn next(&mut self) -> f32 {
34 self.0 ^= self.0 << 13;
35 self.0 ^= self.0 >> 7;
36 self.0 ^= self.0 << 17;
37 ((self.0 >> 40) as f32 / (1u64 << 24) as f32) * 2.0 - 1.0
39 }
40
41 fn unit(&mut self) -> f32 {
43 (self.next() + 1.0) * 0.5
44 }
45
46 fn between(&mut self, low: f32, high: f32) -> f32 {
48 low + (high - low) * self.unit()
49 }
50}
51
52fn noise(kind: &str, count: usize) -> Vec<f32> {
55 let mut random = Random(0x5105_1d00_c0ff_ee11);
56 let (mut b0, mut b1, mut b2, mut b3, mut b4, mut b5, mut b6) = (0f32, 0f32, 0f32, 0f32, 0f32, 0f32, 0f32);
57 let mut last = 0f32;
58 (0..count)
59 .map(|_| {
60 let white = random.next();
61 match kind {
62 "pink" => {
63 b0 = 0.99886 * b0 + white * 0.0555179;
64 b1 = 0.99332 * b1 + white * 0.0750759;
65 b2 = 0.96900 * b2 + white * 0.1538520;
66 b3 = 0.86650 * b3 + white * 0.3104856;
67 b4 = 0.55000 * b4 + white * 0.5329522;
68 b5 = -0.7616 * b5 - white * 0.0168980;
69 let pink = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
70 b6 = white * 0.115926;
71 pink * 0.11
72 }
73 "brown" => {
74 last = (last + 0.02 * white) / 1.02;
75 last * 3.5
76 }
77 _ => white,
78 }
79 })
80 .collect()
81}
82
83pub fn looped(kind: &str, seconds: usize) -> Vec<i16> {
86 let length = seconds * RATE as usize;
87 let raw = noise(kind, length + FADE);
88 let mut out: Vec<f32> = raw[..length].to_vec();
89 for (i, sample) in out.iter_mut().take(FADE).enumerate() {
90 let t = i as f32 / FADE as f32;
91 *sample = raw[i] * t.sqrt() + raw[length + i] * (1.0 - t).sqrt();
92 }
93 let rms = (out.iter().map(|s| s * s).sum::<f32>() / out.len() as f32).sqrt().max(1e-6);
94 let gain = 0.1 / rms;
95 out.iter().map(|s| (s * gain).clamp(-1.0, 1.0)).map(|s| (s * f32::from(i16::MAX)) as i16).collect()
96}
97
98fn wav(samples: &[i16]) -> Vec<u8> {
100 wav_of(samples, 1, RATE)
101}
102
103fn wav_of(samples: &[i16], channels: u16, rate: u32) -> Vec<u8> {
105 let data = u32::try_from(samples.len() * 2).unwrap_or(u32::MAX);
106 let mut out = Vec::with_capacity(44 + samples.len() * 2);
107 out.extend(b"RIFF");
108 out.extend((36 + data).to_le_bytes());
109 out.extend(b"WAVEfmt ");
110 out.extend(16u32.to_le_bytes());
111 out.extend(1u16.to_le_bytes());
112 out.extend(channels.to_le_bytes());
113 out.extend(rate.to_le_bytes());
114 out.extend((rate * 2 * u32::from(channels)).to_le_bytes());
115 out.extend((2 * channels).to_le_bytes());
116 out.extend(16u16.to_le_bytes());
117 out.extend(b"data");
118 out.extend(data.to_le_bytes());
119 for sample in samples {
120 out.extend(sample.to_le_bytes());
121 }
122 out
123}
124
125pub fn noise_file(folder: &Path, kind: &str) -> Result<PathBuf, String> {
127 let (name, bytes): (String, fn(&str) -> Vec<u8>) = if NOISES.contains(&kind) {
128 (format!("{kind}-noise-{NATURE_VERSION}.wav"), |kind| wav(&looped(kind, SECONDS)))
129 } else if NATURE.contains(&kind) {
130 (format!("{kind}-{NATURE_VERSION}.wav"), |kind| {
131 let (left, right) = nature(kind);
132 let interleaved: Vec<i16> = left.iter().zip(&right).flat_map(|(l, r)| [*l, *r]).collect();
133 wav_of(&interleaved, 2, NATURE_RATE)
134 })
135 } else {
136 return Err(format!("{kind}: no such sound"));
137 };
138 let path = folder.join(name);
139 if !path.exists() {
140 std::fs::create_dir_all(folder).map_err(|e| format!("{}: {e}", folder.display()))?;
141 let temporary = path.with_extension("wav.new");
142 std::fs::write(&temporary, bytes(kind)).map_err(|e| format!("{}: {e}", temporary.display()))?;
143 std::fs::rename(&temporary, &path).map_err(|e| format!("{}: {e}", path.display()))?;
144 }
145 Ok(path)
146}
147
148const NATURE_RATE: u32 = 22_050;
152const NATURE_VERSION: u32 = 4;
156
157#[derive(Default, Clone, Copy)]
159struct LowPass(f32);
160
161impl LowPass {
162 fn run(&mut self, x: f32, cutoff: f32) -> f32 {
163 let a = 1.0 - (-2.0 * std::f32::consts::PI * cutoff / NATURE_RATE as f32).exp();
164 self.0 += a * (x - self.0);
165 self.0
166 }
167}
168
169#[derive(Default, Clone, Copy)]
171struct BandPass {
172 x1: f32,
173 x2: f32,
174 y1: f32,
175 y2: f32,
176}
177
178impl BandPass {
179 fn run(&mut self, x: f32, centre: f32, q: f32) -> f32 {
180 let w = 2.0 * std::f32::consts::PI * centre / NATURE_RATE as f32;
181 let alpha = w.sin() / (2.0 * q);
182 let a0 = 1.0 + alpha;
183 let (b0, b2, a1, a2) = (alpha / a0, -alpha / a0, -2.0 * w.cos() / a0, (1.0 - alpha) / a0);
184 let y = b0 * x + b2 * self.x2 - a1 * self.y1 - a2 * self.y2;
185 (self.x2, self.x1, self.y2, self.y1) = (self.x1, x, self.y1, y);
186 y
187 }
188}
189
190fn slow_curve(random: &mut Random, count: usize, every: f32) -> Vec<f32> {
193 let step = (every * NATURE_RATE as f32).max(1.0) as usize;
194 let points: Vec<f32> = (0..count / step + 3).map(|_| random.unit()).collect();
195 (0..count)
196 .map(|i| {
197 let (k, t) = (i / step, (i % step) as f32 / step as f32);
198 let w = (1.0 - (t * std::f32::consts::PI).cos()) * 0.5;
199 points[k] * (1.0 - w) + points[k + 1] * w
200 })
201 .collect()
202}
203
204fn add_ping(out: &mut [f32], at: usize, frequency: f32, decay: f32, amplitude: f32, phase: f32) {
207 let length = (decay * 5.0 * NATURE_RATE as f32) as usize;
208 let w = 2.0 * std::f32::consts::PI * frequency / NATURE_RATE as f32;
209 for (n, sample) in out.iter_mut().skip(at).take(length).enumerate() {
210 let t = n as f32 / NATURE_RATE as f32;
211 *sample += amplitude * (-t / decay).exp() * (w * n as f32 + phase).sin();
212 }
213}
214
215fn events(random: &mut Random, count: usize, rate: f32) -> Vec<usize> {
217 let mut at = 0.0f32;
218 let mut out = Vec::new();
219 loop {
220 at += -(1.0 - random.unit()).max(1e-6).ln() / rate * NATURE_RATE as f32;
221 if at as usize >= count {
222 return out;
223 }
224 out.push(at as usize);
225 }
226}
227
228fn waves(count: usize, seed: u64, schedule: &[(f32, f32, f32, f32)]) -> Vec<f32> {
231 let mut random = Random(seed);
232 let rate = NATURE_RATE as f32;
233 let (mut body, mut foam, mut foam_top, mut surf) = (LowPass::default(), LowPass::default(), LowPass::default(), LowPass::default());
234 (0..count)
235 .map(|i| {
236 let t = i as f32 / rate;
237 let (mut swell, mut fizz) = (0.0f32, 0.0f32);
238 for &(start, rise, fall, strength) in schedule {
239 let since = t - start;
240 if since < 0.0 || since > rise + fall * 4.0 {
241 continue;
242 }
243 if since < rise {
244 swell += strength * (since / rise).powi(2);
245 } else {
246 let after = since - rise;
247 swell += strength * (-after / fall).exp();
248 fizz += strength * (1.0 - (-after / 0.4).exp()) * (-after / (fall * 0.7)).exp();
249 }
250 }
251 let white = random.next();
252 let crash = body.run(white, 150.0 + 1500.0 * swell.min(1.2));
253 let hiss = foam_top.run(white - foam.run(white, 2500.0), 6000.0);
255 let far = surf.run(random.next(), 90.0);
256 crash * (0.15 + swell) * 1.8 + hiss * fizz * 0.25 + far * 0.9
257 })
258 .collect()
259}
260
261fn rain(count: usize, seed: u64, heavy: f32) -> Vec<f32> {
263 let mut random = Random(seed);
264 let mut out = vec![0.0f32; count];
265 let (mut low, mut high) = (LowPass::default(), LowPass::default());
266 let level = slow_curve(&mut random, count, 6.0);
267 let mut pink = Pink::default();
268 for (i, sample) in out.iter_mut().enumerate() {
269 let p = pink.next(random.next());
270 let band = high.run(p, 6500.0) - low.run(p, 450.0);
271 *sample = band * (0.75 + 0.25 * level[i]) * 0.9 * heavy;
272 }
273 for at in events(&mut random, count, 160.0 * heavy) {
274 let (frequency, decay, amplitude, phase) = (random.between(2400.0, 7000.0), random.between(0.0012, 0.004), 0.08 * -(1.0 - random.unit()).max(1e-4).ln().min(4.0), random.between(0.0, 6.28));
275 add_ping(&mut out, at, frequency, decay, amplitude, phase);
276 }
277 for at in events(&mut random, count, 5.0 * heavy) {
278 let (frequency, decay, amplitude, phase) = (random.between(700.0, 2400.0), random.between(0.008, 0.022), random.between(0.05, 0.14), random.between(0.0, 6.28));
279 add_ping(&mut out, at, frequency, decay, amplitude, phase);
280 }
281 out
282}
283
284#[derive(Default)]
286struct Pink([f32; 7]);
287
288impl Pink {
289 fn next(&mut self, white: f32) -> f32 {
290 let b = &mut self.0;
291 b[0] = 0.99886 * b[0] + white * 0.0555179;
292 b[1] = 0.99332 * b[1] + white * 0.0750759;
293 b[2] = 0.96900 * b[2] + white * 0.1538520;
294 b[3] = 0.86650 * b[3] + white * 0.3104856;
295 b[4] = 0.55000 * b[4] + white * 0.5329522;
296 b[5] = -0.7616 * b[5] - white * 0.0168980;
297 let pink = b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6] + white * 0.5362;
298 b[6] = white * 0.115926;
299 pink * 0.11
300 }
301}
302
303fn wind(count: usize, seed: u64, gusts: &[f32]) -> Vec<f32> {
306 let mut random = Random(seed);
307 let mut out = vec![0.0f32; count];
308 let (mut whoosh, mut leaves, mut flutter) = (BandPass::default(), LowPass::default(), LowPass::default());
309 for (i, sample) in out.iter_mut().enumerate() {
310 let gust = 0.2 + 0.8 * gusts[i].powf(1.6);
311 let white = random.next();
312 let air = whoosh.run(white, 220.0 + 900.0 * gust, 0.9);
313 let rustle = white - leaves.run(white, 2400.0);
314 let shiver = flutter.run(random.next().abs(), 25.0) * 6.0;
315 *sample = air * gust * 1.4 + rustle * gust.powi(2) * shiver.min(1.5) * 0.18;
316 }
317 let crackles: Vec<usize> = events(&mut random, count, 140.0);
318 for at in crackles {
319 let gust = gusts[at.min(count - 1)];
320 if random.unit() < gust.powi(2) {
321 let (frequency, decay, amplitude, phase) = (random.between(2800.0, 7500.0), random.between(0.0004, 0.0012), random.between(0.02, 0.07), random.between(0.0, 6.28));
322 add_ping(&mut out, at, frequency, decay, amplitude, phase);
323 }
324 }
325 out
326}
327
328struct Cricket {
331 gain: f32,
332 pitch: f32,
333 period: f32,
334 pulses: usize,
335 offset: f32,
336}
337
338fn crickets(count: usize, seed: u64, band: &[Cricket], trill_gain: f32) -> Vec<f32> {
341 let mut random = Random(seed);
342 let rate = NATURE_RATE as f32;
343 let mut air = LowPass::default();
344 let mut out: Vec<f32> = (0..count).map(|_| air.run(random.next(), 120.0) * 0.25).collect();
345 let pulse = 0.014f32;
346 let gap = 0.016f32;
347 for cricket in band {
348 let mut chirp = cricket.offset;
349 while ((chirp * rate) as usize) < count {
350 for k in 0..cricket.pulses {
351 let start = chirp + k as f32 * (pulse + gap);
352 let first = (start * rate) as usize;
353 let length = (pulse * rate) as usize;
354 let mut phase = 0.0f32;
356 for n in 0..length {
357 let Some(sample) = out.get_mut(first + n) else { break };
358 let t = n as f32 / length as f32;
359 let window = (std::f32::consts::PI * t).sin().powi(2);
360 let pitch = cricket.pitch * (1.0 - 0.015 * t);
361 phase += 2.0 * std::f32::consts::PI * pitch / rate;
362 *sample += cricket.gain * window * phase.sin();
363 }
364 }
365 chirp += cricket.period * random.between(0.95, 1.05);
366 }
367 }
368 let swell = slow_curve(&mut random, count, 3.5);
370 for (i, sample) in out.iter_mut().enumerate() {
371 let t = i as f64 / f64::from(NATURE_RATE);
373 let beat = ((2.0 * std::f64::consts::PI * 40.0 * t).sin().max(0.0).powi(2)) as f32;
374 let on = (swell[i] - 0.35).max(0.0) / 0.65;
375 *sample += trill_gain * on * beat * (2.0 * std::f64::consts::PI * 2800.0 * t).sin() as f32;
376 }
377 out
378}
379
380fn thunder(count: usize, seed: u64, strikes: &[(f32, f32, f32)]) -> Vec<f32> {
382 let mut random = Random(seed);
383 let rate = NATURE_RATE as f32;
384 let (mut low, mut roll) = (LowPass::default(), LowPass::default());
385 let mut brown = 0.0f32;
386 (0..count)
387 .map(|i| {
388 let t = i as f32 / rate;
389 brown = (brown + 0.02 * random.next()) / 1.02;
390 let rumble = low.run(brown * 3.5, 110.0);
391 let rolling = 0.5 + 0.5 * roll.run(random.next(), 4.0) * 8.0;
392 let mut level = 0.0f32;
393 for &(start, rise, length) in strikes {
394 let since = t - start;
395 if since >= 0.0 && since < rise + length * 4.0 {
396 level += if since < rise { since / rise } else { (-(since - rise) / length).exp() };
397 }
398 }
399 rumble * level * rolling.clamp(0.2, 1.4) * 4.0
400 })
401 .collect()
402}
403
404pub fn nature(kind: &str) -> (Vec<i16>, Vec<i16>) {
406 let rate = NATURE_RATE as f32;
407 let seconds: usize = match kind {
408 "waves" => 120,
409 "storm" => 180,
410 "wind" => 90,
411 _ => 60,
412 };
413 let fade = 4 * NATURE_RATE as usize;
414 let count = seconds * NATURE_RATE as usize + fade;
415 let mut plan = Random(0x5105_7a7e_0000_0000 ^ kind.bytes().fold(0u64, |h, b| h.wrapping_mul(31).wrapping_add(u64::from(b))));
416 let (left, right) = match kind {
417 "waves" => {
418 let mut schedule = Vec::new();
420 let mut start = -2.0f32;
421 while start < (count as f32 / rate) {
422 let rise = plan.between(2.0, 3.8);
423 let fall = plan.between(1.6, 2.6);
424 schedule.push((start, rise, fall, plan.between(0.55, 1.0)));
425 start += plan.between(6.5, 11.5);
426 }
427 let late: Vec<(f32, f32, f32, f32)> = schedule.iter().map(|&(s, r, f, k)| (s + 0.25, r, f, k * 0.95)).collect();
428 (waves(count, 11, &schedule), waves(count, 12, &late))
429 }
430 "rain" => (rain(count, 21, 1.0), rain(count, 22, 1.0)),
431 "wind" => {
432 let gusts = slow_curve(&mut plan, count, 4.5);
433 let later: Vec<f32> = (0..count).map(|i| gusts[i.saturating_sub((0.3 * rate) as usize)]).collect();
434 (wind(count, 31, &gusts), wind(count, 32, &later))
435 }
436 "crickets" => {
437 let band: Vec<(f32, Cricket)> = (0..6)
438 .map(|_| {
439 let pan = plan.between(-0.85, 0.85);
440 let near = plan.between(0.15, 1.0);
441 (pan, Cricket { gain: near * 0.35, pitch: plan.between(4100.0, 4900.0), period: plan.between(0.42, 0.95), pulses: if plan.unit() < 0.5 { 3 } else { 4 }, offset: plan.between(0.0, 1.0) })
442 })
443 .collect();
444 let side = |left: bool| -> Vec<Cricket> {
445 band.iter()
446 .map(|(pan, c)| {
447 let angle = (pan + 1.0) * std::f32::consts::FRAC_PI_4;
448 let weight = if left { angle.cos() } else { angle.sin() };
449 Cricket { gain: c.gain * weight, pitch: c.pitch, period: c.period, pulses: c.pulses, offset: c.offset }
450 })
451 .collect()
452 };
453 (crickets(count, 41, &side(true), 0.025), crickets(count, 42, &side(false), 0.035))
454 }
455 _ => {
456 let mut strikes = Vec::new();
458 let mut at = plan.between(4.0, 14.0);
459 while at < (count as f32 / rate) {
460 strikes.push((at, plan.between(0.4, 1.1), plan.between(1.8, 3.5)));
461 at += plan.between(18.0, 42.0);
462 }
463 let far: Vec<(f32, f32, f32)> = strikes.iter().map(|&(s, r, l)| (s + 0.04, r, l)).collect();
464 let mix = |a: Vec<f32>, b: Vec<f32>| a.iter().zip(&b).map(|(x, y)| x * 0.8 + y).collect::<Vec<f32>>();
465 (mix(rain(count, 51, 0.6), thunder(count, 53, &strikes)), mix(rain(count, 52, 0.6), thunder(count, 54, &far)))
466 }
467 };
468 let target = if kind == "crickets" { 0.05 } else { 0.08 };
469 let looped = |raw: &[f32]| -> Vec<f32> {
470 let length = raw.len() - fade;
471 let mut out: Vec<f32> = raw[..length].to_vec();
472 for (i, sample) in out.iter_mut().take(fade).enumerate() {
473 let t = i as f32 / fade as f32;
474 *sample = raw[i] * t.sqrt() + raw[length + i] * (1.0 - t).sqrt();
475 }
476 out
477 };
478 let (left, right) = (looped(&left), looped(&right));
479 let rms = (left.iter().chain(&right).map(|s| s * s).sum::<f32>() / (left.len() + right.len()) as f32).sqrt().max(1e-6);
480 let gain = target / rms;
481 let to_pcm = |channel: &[f32]| channel.iter().map(|s| (s * gain).tanh()).map(|s| (s * f32::from(i16::MAX)) as i16).collect::<Vec<i16>>();
482 (to_pcm(&left), to_pcm(&right))
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn the_random_source_is_centred() {
491 let mut random = Random(7);
492 let values: Vec<f32> = (0..100_000).map(|_| random.next()).collect();
493 let mean = values.iter().sum::<f32>() / values.len() as f32;
494 assert!(mean.abs() < 0.01, "mean {mean}");
495 assert!(values.iter().all(|v| (-1.0..1.0).contains(v)));
496 }
497
498 #[test]
499 fn noises_loop_without_a_seam() {
500 for kind in NOISES {
501 let samples = looped(kind, 3);
502 assert_eq!(samples.len(), 3 * RATE as usize);
503 let step = |a: i16, b: i16| (i32::from(a) - i32::from(b)).abs();
505 let seam = step(samples[samples.len() - 1], samples[0]);
506 let typical = samples.windows(2).map(|w| step(w[0], w[1])).max().unwrap_or(0);
507 assert!(seam <= typical, "{kind}: seam {seam}, steps up to {typical}");
508 }
509 let mean_step = |s: &[i16]| s.windows(2).map(|w| (i32::from(w[0]) - i32::from(w[1])).abs() as i64).sum::<i64>() / s.len() as i64;
511 assert!(mean_step(&looped("brown", 1)) * 4 < mean_step(&looped("white", 1)));
512 }
513
514 fn energy(samples: &[i16], frequency: f32) -> f32 {
516 let mut band = BandPass::default();
517 let power = samples.iter().map(|&x| band.run(f32::from(x) / 32768.0, frequency, 2.0).powi(2)).sum::<f32>() / samples.len() as f32;
518 power / (frequency / 2.0)
519 }
520
521 #[test]
522 fn nature_loops_and_sounds_like_itself() {
523 for kind in NATURE {
524 let (left, right) = nature(kind);
525 assert_eq!(left.len(), right.len());
526 assert!(left.len() >= 60 * NATURE_RATE as usize, "{kind}: a minute at least");
527 let step = |a: i16, b: i16| (i32::from(a) - i32::from(b)).abs();
529 let seam = step(left[left.len() - 1], left[0]);
530 let largest = left.windows(2).map(|w| step(w[0], w[1])).max().unwrap_or(0);
531 assert!(seam <= largest, "{kind}: seam {seam}, steps up to {largest}");
532 assert!(left.iter().all(|s| s.unsigned_abs() < 32000), "{kind}: clipped");
534 }
535 let (crickets, _) = nature("crickets");
537 if let Some(folder) = std::env::var_os("SIOUL_SOUNDS_OUT") {
538 for kind in NATURE {
539 let _ = noise_file(std::path::Path::new(&folder), kind);
540 }
541 }
542 assert!(energy(&crickets, 4500.0) > 5.0 * energy(&crickets, 1000.0));
543 let (waves, _) = nature("waves");
544 assert!(energy(&waves, 150.0) > energy(&waves, 5000.0));
545 }
546
547 #[test]
548 fn a_wav_header_says_what_follows() {
549 let bytes = wav(&[0, 1, -1]);
550 assert_eq!(&bytes[..4], b"RIFF");
551 let stereo = wav_of(&[0, 1, -1, 2], 2, NATURE_RATE);
552 assert_eq!(u16::from_le_bytes(stereo[22..24].try_into().unwrap()), 2);
553 assert_eq!(u32::from_le_bytes(stereo[28..32].try_into().unwrap()), NATURE_RATE * 4);
554 assert_eq!(u32::from_le_bytes(bytes[40..44].try_into().unwrap()), 6);
555 assert_eq!(bytes.len(), 44 + 6);
556 }
557}
558