Skip to main content

banished_audio/
lib.rs

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/// Decoded samples.
17#[derive(Clone)]
18pub struct Pcm {
19    /// The number of channels.
20    pub channels: u16,
21    /// The sample rate, in samples per second.
22    pub sample_rate: u32,
23    /// The samples, interleaved, from -1 to 1.
24    pub samples: Vec<f32>,
25}
26
27/// Decode a `SoundBuffer`; `stream` supplies the packets of streamed music (its `.str` entry).
28pub 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(&params, 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
45/// Load and decode a sound by resource reference (e.g. `Audio\\Music\\Menu.rsc:music`), reading
46/// the `.str` stream of streamed music. Returns the samples and the resource's loop flag.
47pub 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}