Skip to main content

banished_formats/
audio.rs

1//! Audio resources: `Audio::SoundEffect` (Runtime), and the game's `MusicList`,
2//! `AnimAudioEvent`, `AmbientEmitterDescription` and `AmbientEnvironmentDescription`.
3//! See docs/AUDIO.md.
4
5use crate::crc::type_id;
6use crate::crs::{self, Reader};
7use crate::error::{format_err, Result};
8
9/// One playable variant of a sound (0x18 bytes: buffer, pitch range, volume).
10#[derive(Debug, Clone, PartialEq)]
11pub struct SoundVariant {
12    /// `Audio\…\X.rsc:resource` SoundBuffer.
13    pub buffer: Option<String>,
14    /// The lowest pitch, in semitones. `GetSoundBuffer` draws uniformly in
15    /// `[pitch_min, pitch_max]`.
16    pub pitch_min: f32,
17    /// The highest pitch, in semitones.
18    pub pitch_max: f32,
19    /// The volume.
20    pub volume: f32,
21}
22
23/// A sound of the effect's timeline: starts `start` seconds after the effect.
24#[derive(Debug, Clone, PartialEq)]
25pub struct EffectSound {
26    /// When it starts, in seconds after the effect.
27    pub start: f32,
28    /// The variants.
29    pub variants: Vec<SoundVariant>,
30}
31
32/// A sound effect: a timeline of sounds, each with variants, a pitch range and a volume, and the
33/// radius within which it can be heard.
34///
35/// Original: `Audio::SoundEffect::Serialize`, Runtime `0x1800e32f0`.
36#[derive(Debug, Clone, PartialEq)]
37pub struct SoundEffect {
38    // +0x20
39    /// Audible distance (3D effects).
40    pub radius: f32,
41    // +0x24
42    /// Full-volume distance.
43    pub inner_radius: f32,
44    /// The timeline's sounds.
45    pub sounds: Vec<EffectSound>,
46    // +0x28
47    /// The effect's length in microseconds; 0 when a sound loops (the effect never ends). The
48    /// engine computes it when it compiles the effect.
49    ///
50    /// Original: `Audio::SoundEffect::Compile`, Runtime `0x1800e31f0`.
51    pub max_length: i64,
52}
53
54/// Read a [`SoundEffect`] from the bytes of its resource.
55pub fn sound_effect(data: &[u8]) -> Result<SoundEffect> {
56    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
57        if h.type_id != type_id("SoundEffect") {
58            return format_err(format!("expected SoundEffect, found {:#010x}", h.type_id));
59        }
60        r.block(|r, version| {
61            let radius = r.f32()?;
62            let inner_radius = r.f32()?;
63            let sounds = (0..r.count()?)
64                .map(|_| {
65                    let start = r.f32()?;
66                    let variants = (0..r.count()?)
67                        .map(|_| {
68                            Ok(SoundVariant {
69                                buffer: r.external_ref()?,
70                                pitch_min: r.f32()?,
71                                pitch_max: r.f32()?,
72                                volume: r.f32()?,
73                            })
74                        })
75                        .collect::<Result<_>>()?;
76                    Ok(EffectSound { start, variants })
77                })
78                .collect::<Result<_>>()?;
79            let max_length = if version >= 1 { r.i64()? } else { 0 };
80            Ok(SoundEffect {
81                radius,
82                inner_radius,
83                sounds,
84                max_length,
85            })
86        })
87    })?;
88    Ok(value)
89}
90
91/// One `MusicList` track (0x18 bytes): played when the month and the town's mood are in range.
92///
93/// Original: `FUN_1400a95b0`.
94#[derive(Debug, Clone, PartialEq)]
95pub struct MusicTrack {
96    /// The music's sound effect.
97    pub effect: Option<String>,
98    // +0x8, +0xc
99    /// Mood range (health and happiness fractions, averaged).
100    pub mood: (f32, f32),
101    // +0x10, +0x14
102    /// Month range, inclusive.
103    pub months: (i32, i32),
104}
105
106/// `MusicList` (`Game/MusicTracks.rsc`).
107pub fn music_list(data: &[u8]) -> Result<Vec<MusicTrack>> {
108    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
109        if h.type_id != type_id("MusicList") {
110            return format_err(format!("expected MusicList, found {:#010x}", h.type_id));
111        }
112        r.block(|r, _| {
113            (0..r.count()?)
114                .map(|_| {
115                    let mood = (r.f32()?, r.f32()?);
116                    let months = (r.i32()?, r.i32()?);
117                    Ok(MusicTrack {
118                        effect: r.external_ref()?,
119                        mood,
120                        months,
121                    })
122                })
123                .collect()
124        })
125    })?;
126    Ok(value)
127}
128
129/// `AnimAudioEvent`: an animation event that plays a 3D effect at the entity (sound type 4) with
130/// probability `probability`.
131///
132/// Original: `FUN_14008d310`.
133#[derive(Debug, Clone, PartialEq)]
134pub struct AnimAudioEvent {
135    /// The effect.
136    pub effect: Option<String>,
137    /// The probability that it plays.
138    pub probability: f32,
139}
140
141/// Read a [`AnimAudioEvent`] from the bytes of its resource.
142pub fn anim_audio_event(data: &[u8]) -> Result<AnimAudioEvent> {
143    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
144        if h.type_id != type_id("AnimAudioEvent") {
145            return format_err(format!(
146                "expected AnimAudioEvent, found {:#010x}",
147                h.type_id
148            ));
149        }
150        r.block(|r, _| {
151            Ok(AnimAudioEvent {
152                effect: r.external_ref()?,
153                probability: r.f32()?,
154            })
155        })
156    })?;
157    Ok(value)
158}
159
160// +0x20
161/// `AmbientEmitterDescription`: the effects an ambient emitter feeds, and whether activation starts
162/// them.
163///
164/// Original: `FUN_140043b10`.
165#[derive(Debug, Clone, PartialEq)]
166pub struct AmbientEmitterDescription {
167    /// The effects.
168    pub effects: Vec<Option<String>>,
169    /// Whether activation starts them.
170    pub autostart: bool,
171}
172
173/// Read a [`AmbientEmitterDescription`] from the bytes of its resource.
174pub fn ambient_emitter_description(data: &[u8]) -> Result<AmbientEmitterDescription> {
175    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
176        if h.type_id != type_id("AmbientEmitterDescription") {
177            return format_err(format!(
178                "expected AmbientEmitterDescription, found {:#010x}",
179                h.type_id
180            ));
181        }
182        r.block(|r, _| {
183            let effects = (0..r.count()?)
184                .map(|_| r.external_ref())
185                .collect::<Result<_>>()?;
186            Ok(AmbientEmitterDescription {
187                effects,
188                autostart: r.bool()?,
189            })
190        })
191    })?;
192    Ok(value)
193}
194
195/// One terrain class of an `AmbientEnvironmentDescription` (0x28 bytes).
196#[derive(Debug, Clone, PartialEq)]
197pub struct AmbientTerrain {
198    /// Cell types (bit = map cell type) this class counts.
199    pub cell_mask: u32,
200    /// When set, only cells whose entity uses this template count.
201    pub entity: Option<String>,
202    /// The warm effect, played at `t` of the temperature cross-fade.
203    pub warm: Option<String>,
204    /// The cold effect, played at `1 - t` of the temperature cross-fade.
205    pub cold: Option<String>,
206    /// The weight it adds for each cell counted.
207    pub weight: i32,
208}
209
210/// `AmbientEnvironmentDescription`.
211///
212/// Original: `FUN_1400442b0`.
213#[derive(Debug, Clone, PartialEq)]
214pub struct AmbientEnvironmentDescription {
215    /// The terrain classes.
216    pub terrains: Vec<AmbientTerrain>,
217    // +0x30, +0x34; +0x18
218    /// Environment range of the cross-fade.
219    pub fade: (f32, f32),
220}
221
222/// Read a [`AmbientEnvironmentDescription`] from the bytes of its resource.
223pub fn ambient_environment_description(data: &[u8]) -> Result<AmbientEnvironmentDescription> {
224    let (_, value) = crs::parse(data, |r: &mut Reader, h| {
225        if h.type_id != type_id("AmbientEnvironmentDescription") {
226            return format_err(format!(
227                "expected AmbientEnvironmentDescription, found {:#010x}",
228                h.type_id
229            ));
230        }
231        r.block(|r, _| {
232            let terrains = (0..r.count()?)
233                .map(|_| {
234                    Ok(AmbientTerrain {
235                        cell_mask: r.u32()?,
236                        entity: r.external_ref()?,
237                        warm: r.external_ref()?,
238                        cold: r.external_ref()?,
239                        weight: r.i32()?,
240                    })
241                })
242                .collect::<Result<_>>()?;
243            Ok(AmbientEnvironmentDescription {
244                terrains,
245                fade: (r.f32()?, r.f32()?),
246            })
247        })
248    })?;
249    Ok(value)
250}