1use std::collections::HashMap;
23use std::rc::Rc;
24use std::time::Duration;
25
26use banished_formats::audio::SoundEffect;
27use glam::Vec3;
28
29use crate::device::{Channel, PlayParams, Sound};
30
31const EFFECTS: usize = 0x100;
33const EFFECTS_3D: usize = 0x1000;
34const SOUNDS: usize = 8;
36const UNIT: f32 = 2.328_306_4e-10;
38const RETRIGGER_DIVISOR: f32 = 20.0;
40
41pub mod sound_type {
45 pub const UI: u32 = 1;
47 pub const MUSIC: u32 = 2;
49 pub const AMBIENT: u32 = 3;
51 pub const EFFECT: u32 = 4;
53}
54
55pub trait Mixer {
57 fn play(&mut self, sound: &Sound, params: PlayParams) -> Option<Channel>;
59 fn stop(&mut self, channel: Channel);
61 fn set_volume(&mut self, channel: Channel, volume: f32);
63}
64
65impl Mixer for crate::Device {
66 fn play(&mut self, sound: &Sound, params: PlayParams) -> Option<Channel> {
67 crate::Device::play(self, sound, params).ok().flatten()
68 }
69 fn stop(&mut self, channel: Channel) {
70 crate::Device::stop(self, channel)
71 }
72 fn set_volume(&mut self, channel: Channel, volume: f32) {
73 crate::Device::set_volume(self, channel, volume)
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq)]
82pub struct Volumes {
83 pub ui: i32,
85 pub music: i32,
87 pub ambient: i32,
89 pub effect: i32,
91}
92
93impl Default for Volumes {
94 fn default() -> Self {
95 Volumes {
96 ui: 100,
97 music: 50,
98 ambient: 100,
99 effect: 100,
100 }
101 }
102}
103
104impl Volumes {
105 fn table(&self) -> [f32; 5] {
107 [
108 0.0,
109 self.ui as f32 / 100.0,
110 self.music as f32 / 100.0,
111 self.ambient as f32 / 100.0,
112 self.effect as f32 / 100.0,
113 ]
114 }
115}
116
117pub struct Effect {
119 pub reference: String,
121 pub desc: SoundEffect,
123 pub buffers: Vec<Vec<Option<Sound>>>,
126}
127
128impl Effect {
129 fn max_length(&self) -> i64 {
131 self.desc.max_length
132 }
133}
134
135#[derive(Default)]
137pub struct EffectCache {
138 effects: HashMap<String, Option<Rc<Effect>>>,
139}
140
141impl EffectCache {
142 pub fn get(
144 &mut self,
145 archive: &banished_formats::pkg::Archive,
146 reference: &str,
147 ) -> Option<Rc<Effect>> {
148 let key = reference.replace('/', "\\");
149 if !self.effects.contains_key(&key) {
150 self.preload(archive, &[key.as_str()]);
151 }
152 self.effects[&key].clone()
153 }
154
155 pub fn preload(&mut self, archive: &banished_formats::pkg::Archive, references: &[&str]) {
158 let missing = self.missing(references);
159 if !missing.is_empty() {
160 self.add(Self::prepare(archive, &missing));
161 }
162 }
163
164 pub fn missing<'a>(&self, references: &[&'a str]) -> Vec<&'a str> {
166 references
167 .iter()
168 .copied()
169 .filter(|r| !self.effects.contains_key(&r.replace('/', "\\")))
170 .collect()
171 }
172
173 pub fn prepare(archive: &banished_formats::pkg::Archive, references: &[&str]) -> Prepared {
177 let mut keys: Vec<String> = Vec::new();
178 for r in references {
179 let key = r.replace('/', "\\");
180 if !keys.contains(&key) {
181 keys.push(key);
182 }
183 }
184 let descs: Vec<Result<SoundEffect, Option<String>>> = keys
185 .iter()
186 .map(|key| {
187 let path = key.replace('\\', "/");
188 let bytes = archive.read_ref(&path).map_err(|_| None)?;
189 banished_formats::audio::sound_effect(&bytes).map_err(|e| Some(e.to_string()))
190 })
191 .collect();
192 let buffers: Vec<Option<String>> = descs
193 .iter()
194 .flatten()
195 .flat_map(|d| d.sounds.iter().flat_map(|s| &s.variants))
196 .map(|v| Some(v.buffer.as_deref()?.replace('\\', "/")))
197 .collect();
198 let decoded = banished_formats::par::map(&buffers, |r| {
199 r.as_deref().map(|r| crate::load_sound(archive, r))
200 });
201 Prepared {
202 keys,
203 descs,
204 buffers,
205 decoded,
206 }
207 }
208
209 pub fn add(&mut self, prepared: Prepared) {
211 let Prepared {
212 keys,
213 descs,
214 buffers,
215 decoded,
216 } = prepared;
217 let mut decoded = buffers.iter().zip(decoded).map(|(r, d)| match d? {
218 Ok((pcm, looping)) => Some(Sound::new(pcm, looping)),
219 Err(e) => {
220 log::warn!("{}: {e}", r.as_deref().unwrap_or_default());
221 None
222 }
223 });
224 for (key, desc) in keys.into_iter().zip(descs) {
225 let desc = desc.map_err(|e| e.map(|e| log::warn!("{key}: {e}"))).ok();
226 let loaded = desc.map(|desc| {
227 let buffers = desc
228 .sounds
229 .iter()
230 .map(|s| decoded.by_ref().take(s.variants.len()).collect())
231 .collect();
232 Rc::new(Effect {
233 reference: key.clone(),
234 desc,
235 buffers,
236 })
237 });
238 self.effects.entry(key).or_insert(loaded);
239 }
240 }
241}
242
243pub struct Prepared {
245 keys: Vec<String>,
246 descs: Vec<Result<SoundEffect, Option<String>>>,
248 buffers: Vec<Option<String>>,
250 decoded: Vec<Option<Result<(crate::Pcm, bool), String>>>,
251}
252
253struct Effect2D {
255 effect: Rc<Effect>,
256 kind: u32,
257 elapsed: i64,
259 volume: f32,
261 variant_volume: f32,
263 channels: [Option<Channel>; SOUNDS],
264 flags: [u8; SOUNDS],
266 handle: u32,
267 started: bool,
269}
270
271struct Effect3D {
273 position: Vec3,
274 kind: u32,
275 effect: Rc<Effect>,
276 elapsed: i64,
278 playing: u32,
280 handle: u32,
281 volume: f32,
283 emitters: Vec<(Vec3, f32)>,
285}
286
287pub struct Scene {
292 step: i64,
294 counter: u32,
296 effects: Vec<Option<Effect2D>>,
297 effects3d: Vec<Option<Effect3D>>,
298 paused: [bool; 5],
300 pub volumes: Volumes,
302}
303
304impl Default for Scene {
305 fn default() -> Self {
306 Scene {
307 step: 0,
308 counter: 1,
309 effects: (0..EFFECTS).map(|_| None).collect(),
310 effects3d: (0..EFFECTS_3D).map(|_| None).collect(),
311 paused: [false; 5],
312 volumes: Volumes::default(),
313 }
314 }
315}
316
317fn index2d(handle: u32) -> usize {
318 (handle >> 1 & 0xff) as usize
319}
320
321fn index3d(handle: u32) -> usize {
322 (handle >> 1 & 0xfff) as usize
323}
324
325fn volume_for_position(at: Vec3, radius: f32, inner: f32, listener: Vec3) -> f32 {
327 let d = (at - listener).length();
328 if radius < d {
329 0.0
330 } else if inner <= d {
331 1.0 - (d - inner) / (radius - inner)
332 } else {
333 1.0
334 }
335}
336
337fn micros(seconds: f32) -> i64 {
338 (seconds as f64 * 1e6) as i64
339}
340
341impl Scene {
342 fn effect_mut(&mut self, handle: u32) -> Option<&mut Effect2D> {
343 self.effects
344 .get_mut(index2d(handle))?
345 .as_mut()
346 .filter(|e| (e.handle ^ handle) & 0xffff_fe00 == 0)
347 }
348
349 fn effect3d_mut(&mut self, handle: u32) -> Option<&mut Effect3D> {
350 self.effects3d
351 .get_mut(index3d(handle))?
352 .as_mut()
353 .filter(|e| (e.handle ^ handle) & 0xffff_e000 == 0)
354 }
355
356 pub fn play_effect(&mut self, effect: &Rc<Effect>, kind: u32, start: i64) -> u32 {
361 let Some(i) = self.effects.iter().position(Option::is_none) else {
362 return 0;
363 };
364 self.counter = self.counter.wrapping_add(1);
365 let handle = (i as u32) * 2 | self.counter << 9;
366 self.effects[i] = Some(Effect2D {
367 effect: effect.clone(),
368 kind,
369 elapsed: start,
370 volume: 1.0,
371 variant_volume: 1.0,
372 channels: [None; SOUNDS],
373 flags: [0; SOUNDS],
374 handle,
375 started: false,
376 });
377 handle
378 }
379
380 pub fn play_effect_3d(&mut self, effect: &Rc<Effect>, kind: u32, start: i64, at: Vec3) -> u32 {
385 let mut window = self.step;
386 if effect.max_length() != 0 {
387 let w = micros(effect.max_length() as f32 / 1e6 / RETRIGGER_DIVISOR);
388 if (window as f32) < w as f32 {
389 window = w;
390 }
391 }
392 let radius = effect.desc.radius;
393 let mut free = None;
394 for (i, slot) in self.effects3d.iter().enumerate() {
395 match slot {
396 None => {
397 if free.is_none() {
398 free = Some(i);
399 }
400 }
401 Some(e) => {
402 if Rc::ptr_eq(&e.effect, effect)
403 && (e.position - at).length_squared() < radius * radius
404 && (e.elapsed - start).abs() < window
405 {
406 return 0;
407 }
408 }
409 }
410 }
411 let Some(i) = free else { return 0 };
412 self.counter = self.counter.wrapping_add(1);
413 let handle = (i as u32) * 2 | 1 | self.counter << 13;
414 self.effects3d[i] = Some(Effect3D {
415 position: at,
416 kind,
417 effect: effect.clone(),
418 elapsed: start,
419 playing: 0,
420 handle,
421 volume: 1.0,
422 emitters: Vec::new(),
423 });
424 handle
425 }
426
427 pub fn find_effect_3d(&self, effect: &Rc<Effect>) -> u32 {
429 self.effects3d
430 .iter()
431 .flatten()
432 .find(|e| Rc::ptr_eq(&e.effect, effect))
433 .map_or(0, |e| e.handle)
434 }
435
436 pub fn add_emitter(&mut self, handle: u32, at: Vec3, volume: f32) {
438 if let Some(e) = self.effect3d_mut(handle) {
439 e.emitters.push((at, volume));
440 }
441 }
442
443 pub fn remove_emitter(&mut self, handle: u32, at: Vec3) {
445 if let Some(e) = self.effect3d_mut(handle) {
446 if let Some(i) = e.emitters.iter().position(|(p, _)| *p == at) {
447 e.emitters.remove(i);
448 }
449 }
450 }
451
452 pub fn move_emitter(&mut self, handle: u32, from: Vec3, to: Vec3) {
454 if let Some(e) = self.effect3d_mut(handle) {
455 if let Some(m) = e.emitters.iter_mut().find(|(p, _)| *p == from) {
456 m.0 = to;
457 }
458 }
459 }
460
461 pub fn set_emitter_volume(&mut self, handle: u32, at: Vec3, volume: f32) {
463 if let Some(e) = self.effect3d_mut(handle) {
464 if let Some(m) = e.emitters.iter_mut().find(|(p, _)| *p == at) {
465 m.1 = volume;
466 }
467 }
468 }
469
470 pub fn emitter_count(&self, handle: u32) -> usize {
472 self.effects3d
473 .get(index3d(handle))
474 .and_then(Option::as_ref)
475 .filter(|e| (e.handle ^ handle) & 0xffff_e000 == 0)
476 .map_or(0, |e| e.emitters.len())
477 }
478
479 pub fn is_effect_playing(&self, handle: u32) -> bool {
481 self.effects
482 .get(index2d(handle))
483 .and_then(Option::as_ref)
484 .is_some_and(|e| (e.handle ^ handle) & 0xffff_fe00 == 0)
485 }
486
487 pub fn stop_effect(&mut self, mixer: &mut dyn Mixer, handle: u32) {
489 if let Some(e) = self.effect_mut(handle) {
490 for c in e.channels.iter().flatten() {
491 mixer.stop(*c);
492 }
493 self.effects[index2d(handle)] = None;
494 }
495 }
496
497 pub fn stop_effect_3d(&mut self, mixer: &mut dyn Mixer, handle: u32) {
499 let Some(e) = self.effect3d_mut(handle) else {
500 return;
501 };
502 let playing = e.playing;
503 if playing != 0 {
504 self.stop_effect(mixer, playing);
505 }
506 self.effects3d[index3d(handle)] = None;
507 }
508
509 pub fn stop_all(&mut self, mixer: &mut dyn Mixer) {
511 for slot in &mut self.effects {
512 if let Some(e) = slot.take() {
513 for c in e.channels.iter().flatten() {
514 mixer.stop(*c);
515 }
516 }
517 }
518 }
519
520 fn pause_effect(e: &mut Effect2D, mixer: &mut dyn Mixer) {
522 for (c, f) in e.channels.iter_mut().zip(&mut e.flags) {
523 if let Some(ch) = c.take() {
524 mixer.stop(ch);
525 *f &= !2;
526 }
527 }
528 }
529
530 pub fn pause(&mut self, mixer: &mut dyn Mixer, kind: u32, paused: bool) {
532 let k = kind as usize;
533 if k >= self.paused.len() || self.paused[k] == paused {
534 return;
535 }
536 for e in self.effects.iter_mut().flatten().filter(|e| e.kind == kind) {
537 if paused {
538 Self::pause_effect(e, mixer);
539 } else {
540 e.started = false;
541 }
542 }
543 self.paused[k] = paused;
544 }
545
546 pub fn is_paused(&self, kind: u32) -> bool {
548 self.paused.get(kind as usize).copied().unwrap_or(false)
549 }
550
551 pub fn on_ended(&mut self, channel: Channel) {
553 for e in self.effects.iter_mut().flatten() {
554 for (c, f) in e.channels.iter_mut().zip(&mut e.flags) {
555 if *c == Some(channel) {
556 *c = None;
557 *f |= 2;
558 }
559 }
560 }
561 }
562
563 pub fn update(
569 &mut self,
570 mixer: &mut dyn Mixer,
571 dt: i64,
572 step: i64,
573 rand: &mut dyn FnMut() -> u32,
574 listener: Vec3,
575 reach: f32,
576 ) {
577 self.step = step;
578 for i in 0..EFFECTS_3D {
580 let Some(e) = &self.effects3d[i] else {
581 continue;
582 };
583 if self.paused[e.kind as usize % 5] {
584 continue;
585 }
586 let volume = self.attenuated_volume(e, listener, reach);
587 let (handle, kind, effect, playing, elapsed) =
588 (e.handle, e.kind, e.effect.clone(), e.playing, e.elapsed);
589 let mut playing = playing;
590 if volume == 0.0 && playing != 0 {
591 self.stop_effect(mixer, playing);
592 playing = 0;
593 } else if volume > 0.0 && playing == 0 {
594 playing = self.play_effect(&effect, kind, elapsed);
595 }
596 if let Some(p) = self.effect_mut(playing) {
597 p.volume = volume;
598 }
599 let e = self.effects3d[i].as_mut().unwrap();
600 e.playing = playing;
601 if playing == 0 && effect.max_length() != 0 && effect.max_length() <= e.elapsed {
602 self.stop_effect_3d(mixer, handle);
603 } else {
604 e.elapsed += dt;
605 }
606 }
607 let volumes = self.volumes.table();
609 for i in 0..EFFECTS {
610 let Some(e) = self.effects[i].as_mut() else {
611 continue;
612 };
613 if self.paused[e.kind as usize % 5] {
614 continue;
615 }
616 let from = if e.started { e.elapsed } else { 0 };
617 e.started = true;
618 let to = e.elapsed + dt;
619 let mut done = false;
620 if e.kind != 0 {
621 let type_volume = volumes[e.kind as usize % 5];
622 let mut ended = 0;
623 for (s, sound) in e.effect.desc.sounds.iter().enumerate().take(SOUNDS) {
624 let at = micros(sound.start);
625 if at < from || to <= at {
626 if e.flags[s] & 1 != 0 && e.flags[s] & 2 == 0 {
628 if let Some(c) = e.channels[s] {
629 mixer.set_volume(c, e.variant_volume * e.volume * type_volume);
630 }
631 }
632 } else {
633 let n = sound.variants.len() as u32;
638 let v = if n == 0 { 0 } else { (rand() % n) as usize };
639 let r = rand();
640 let Some(variant) = sound.variants.get(v) else {
641 e.flags[s] |= 3;
642 ended += 1;
643 continue;
644 };
645 let pitch = (variant.pitch_max - variant.pitch_min) * r as f32 * UNIT
646 + variant.pitch_min;
647 e.variant_volume = variant.volume;
648 let channel = e.effect.buffers[s][v].as_ref().and_then(|buffer| {
649 mixer.play(
650 buffer,
651 PlayParams {
652 offset: Duration::from_micros(e.elapsed.max(0) as u64),
653 volume: variant.volume * e.volume * type_volume,
654 pitch,
655 looping: None,
656 },
657 )
658 });
659 e.channels[s] = channel;
660 e.flags[s] |= 1;
661 if channel.is_none() {
662 e.flags[s] |= 2;
664 }
665 }
666 if e.flags[s] & 2 != 0 {
667 ended += 1;
668 }
669 }
670 done = ended == e.effect.desc.sounds.len().min(SOUNDS);
671 }
672 e.elapsed = to;
673 if done {
674 let handle = e.handle;
675 self.effects[i] = None;
676 for slot in &mut self.effects3d {
677 if slot.as_ref().is_some_and(|f| f.playing == handle) {
678 *slot = None;
679 }
680 }
681 }
682 }
683 }
684
685 fn attenuated_volume(&self, e: &Effect3D, listener: Vec3, reach: f32) -> f32 {
687 let radius = reach + e.effect.desc.radius;
688 let inner = e.effect.desc.inner_radius;
689 let v = if e.emitters.is_empty() {
690 volume_for_position(e.position, radius, inner, listener)
691 } else {
692 e.emitters.iter().fold(0.0f32, |best, (p, w)| {
693 best.max(volume_for_position(*p, radius, inner, listener) * w)
694 })
695 };
696 v * e.volume
697 }
698
699 pub fn active(&self) -> (usize, usize) {
701 (
702 self.effects.iter().flatten().count(),
703 self.effects3d.iter().flatten().count(),
704 )
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711 use banished_formats::audio::{EffectSound, SoundVariant};
712
713 #[derive(Default)]
714 struct Fake {
715 next: u32,
716 playing: Vec<(Channel, f32, f32)>,
717 stopped: Vec<Channel>,
718 }
719
720 impl Mixer for Fake {
721 fn play(&mut self, _: &Sound, p: PlayParams) -> Option<Channel> {
722 self.next += 1;
723 let c = Channel::test(self.next);
724 self.playing.push((c, p.volume, p.pitch));
725 Some(c)
726 }
727 fn stop(&mut self, c: Channel) {
728 self.stopped.push(c);
729 }
730 fn set_volume(&mut self, _: Channel, _: f32) {}
731 }
732
733 fn effect(starts: &[f32], radius: f32, max_length: i64) -> Rc<Effect> {
734 let pcm = crate::Pcm {
735 channels: 1,
736 sample_rate: 100,
737 samples: vec![0.0; 10],
738 };
739 let sound = Sound::new(pcm, false);
740 Rc::new(Effect {
741 reference: "test".into(),
742 desc: SoundEffect {
743 radius,
744 inner_radius: 1.0,
745 sounds: starts
746 .iter()
747 .map(|&s| EffectSound {
748 start: s,
749 variants: vec![SoundVariant {
750 buffer: Some("x".into()),
751 pitch_min: -1.0,
752 pitch_max: 1.0,
753 volume: 0.5,
754 }],
755 })
756 .collect(),
757 max_length,
758 },
759 buffers: starts.iter().map(|_| vec![Some(sound.clone())]).collect(),
760 })
761 }
762
763 #[test]
764 fn timeline_starts_sounds_in_their_window_and_frees_when_ended() {
765 let mut scene = Scene::default();
766 let mut mixer = Fake::default();
767 let fx = effect(&[0.0, 0.5], 10.0, 1_000_000);
768 let h = scene.play_effect(&fx, sound_type::EFFECT, 0);
769 let mut seq = 0u32;
770 let mut rand = || {
771 seq += 1;
772 seq
773 };
774 scene.update(&mut mixer, 100_000, 0, &mut rand, Vec3::ZERO, 0.0);
775 assert_eq!(mixer.playing.len(), 1);
776 assert_eq!(mixer.playing[0].1, 0.5);
778 scene.update(&mut mixer, 500_000, 0, &mut rand, Vec3::ZERO, 0.0);
779 assert_eq!(mixer.playing.len(), 2);
780 let (a, b) = (mixer.playing[0].0, mixer.playing[1].0);
781 scene.on_ended(a);
782 scene.update(&mut mixer, 100_000, 0, &mut rand, Vec3::ZERO, 0.0);
783 assert!(scene.is_effect_playing(h));
784 scene.on_ended(b);
785 scene.update(&mut mixer, 100_000, 0, &mut rand, Vec3::ZERO, 0.0);
786 assert!(!scene.is_effect_playing(h));
787 }
788
789 #[test]
790 fn music_type_uses_its_volume_and_type_zero_is_silent() {
791 let mut scene = Scene::default();
792 let mut mixer = Fake::default();
793 let fx = effect(&[0.0], 10.0, 0);
794 scene.play_effect(&fx, sound_type::MUSIC, 0);
795 scene.play_effect(&fx, 0, 0);
796 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
797 assert_eq!(mixer.playing.len(), 1);
798 assert_eq!(mixer.playing[0].1, 0.25);
799 }
800
801 #[test]
802 fn three_d_effect_follows_the_loudest_emitter() {
803 let mut scene = Scene::default();
804 let mut mixer = Fake::default();
805 let fx = effect(&[0.0], 10.0, 0);
806 let h = scene.play_effect_3d(&fx, sound_type::AMBIENT, 0, Vec3::ZERO);
807 scene.add_emitter(h, Vec3::new(100.0, 0.0, 0.0), 1.0);
808 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
810 assert_eq!(scene.active(), (0, 1));
811 scene.add_emitter(h, Vec3::new(5.5, 0.0, 0.0), 1.0);
813 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
814 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
815 assert_eq!(scene.active(), (1, 1));
816 assert!((mixer.playing[0].1 - 0.25).abs() < 1e-6);
817 assert_eq!(scene.emitter_count(h), 2);
818 assert_eq!(scene.find_effect_3d(&fx), h);
819 }
820
821 #[test]
822 fn pause_stops_channels_and_resume_continues() {
823 let mut scene = Scene::default();
824 let mut mixer = Fake::default();
825 let fx = effect(&[0.0], 10.0, 0);
826 scene.play_effect(&fx, sound_type::MUSIC, 0);
827 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
828 scene.pause(&mut mixer, sound_type::MUSIC, true);
829 assert_eq!(mixer.stopped.len(), 1);
830 scene.pause(&mut mixer, sound_type::MUSIC, false);
831 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
832 assert_eq!(mixer.playing.len(), 2);
835 scene.update(&mut mixer, 1, 0, &mut || 0, Vec3::ZERO, 0.0);
836 assert_eq!(mixer.playing.len(), 2);
837 }
838}