1#![doc = include_str!("../README.md")]
2#![doc(
3 html_logo_url = "https://tanepiper.github.io/unbanished/assets/un-banished-icon.svg",
4 html_favicon_url = "https://tanepiper.github.io/unbanished/favicon.svg"
5)]
6
7mod device;
8pub mod scene;
9
10pub use device::{pitch_ratio, Channel, Device, PlayParams, Slots, Sound, CHANNELS};
11pub use scene::{sound_type, Effect, EffectCache, Mixer, Prepared, Scene, Volumes};
12
13use banished_formats::pkg::Archive;
14use banished_formats::types::{self, SoundBuffer};
15
16#[derive(Clone)]
18pub struct Pcm {
19 pub channels: u16,
21 pub sample_rate: u32,
23 pub samples: Vec<f32>,
25}
26
27pub fn decode(sound: &SoundBuffer, stream: Option<&[u8]>) -> Result<Pcm, String> {
29 let f = &sound.format;
30 let params = banished_wma::Params::xwma(
31 f.channels,
32 f.sample_rate,
33 f.avg_bytes_per_sec,
34 f.block_align,
35 );
36 let packets = stream.unwrap_or(&sound.data);
37 let pcm = banished_wma::decode(¶ms, packets).map_err(|e| format!("decode: {e:?}"))?;
38 Ok(Pcm {
39 channels: f.channels,
40 sample_rate: f.sample_rate,
41 samples: pcm.into_iter().map(|s| s as f32 / 32768.0).collect(),
42 })
43}
44
45pub fn load_sound(archive: &Archive, reference: &str) -> Result<(Pcm, bool), String> {
48 let sound = types::sound_buffer(&archive.read_ref(reference).map_err(|e| e.to_string())?)
49 .map_err(|e| e.to_string())?;
50 let stream = if sound.streamed {
51 Some(
52 archive
53 .read(&sound.stream_entry)
54 .map_err(|e| e.to_string())?,
55 )
56 } else {
57 None
58 };
59 Ok((decode(&sound, stream.as_deref())?, sound.looping))
60}