use macros::named_list_chunk;
use crate::prelude::*;
use crate::wad::deserialize::reader::DataReader;
use crate::wad::elements::GMElement;
use crate::wad::elements::audio::GMAudio;
use crate::wad::elements::audio_group::GMAudioGroup;
use crate::wad::reference::GMRef;
use crate::wad::serialize::builder::DataBuilder;
use crate::wad::version::GMVersion;
#[named_list_chunk("SOND")]
pub struct GMSounds {
pub sounds: Vec<GMSound>,
pub exists: bool,
}
impl GMElement for GMSounds {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let sounds: Vec<GMSound> = reader.read_pointer_list()?;
Ok(Self { sounds, exists: true })
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
builder.write_pointer_list(&self.sounds)?;
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GMSound {
pub name: String,
pub flags: Flags,
pub audio_type: AudioType,
pub file: String,
pub effects: u32,
pub volume: f32,
pub pitch: f32,
pub audio_group: GMRef<GMAudioGroup>,
pub audio_file: Option<GMRef<GMAudio>>,
pub audio_length: Option<f32>,
}
impl GMElement for GMSound {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let name: String = reader.read_gm_string()?;
let flags = Flags::deserialize(reader)?;
let audio_type: Option<String> = reader.read_gm_string_opt()?;
let audio_type = match audio_type.as_deref() {
Some("") | None => AudioType::Unknown,
Some(".wav") => AudioType::Wav,
Some(".ogg") => AudioType::Ogg,
Some(".mp3") => AudioType::Mp3,
Some(other) => {
reader.warn_invalid_const(format!("Invalid or unknown audio type {other:?}"))?;
AudioType::Unknown
}
};
let file: String = reader.read_gm_string()?;
let effects = reader.read_u32()?;
let volume = reader.read_f32()?;
let pitch = reader.read_f32()?;
let audio_group: GMRef<GMAudioGroup> = if reader.general_info.wad_version >= 14 {
reader.read_resource_by_id()?
} else {
let preload = reader.read_bool32().context("reading preload")?;
reader.assert_bool(preload, true, "Preload")?;
GMRef::new(get_builtin_sound_group_id(&reader.general_info.version))
};
let audio_file: Option<GMRef<GMAudio>> = reader.read_resource_by_id_opt()?;
let audio_length: Option<f32> = reader.deserialize_if_gm_version((2024, 6))?;
Ok(Self {
name,
flags,
audio_type,
file,
effects,
volume,
pitch,
audio_group,
audio_file,
audio_length,
})
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
builder.write_gm_string(&self.name);
self.flags.serialize(builder)?;
let audio_type = match self.audio_type {
AudioType::Unknown => None,
AudioType::Wav => Some(".wav"),
AudioType::Ogg => Some(".ogg"),
AudioType::Mp3 => Some(".mp3"),
};
let audio_type = audio_type.map(String::from);
builder.write_gm_string_opt(&audio_type);
builder.write_gm_string(&self.file);
builder.write_u32(self.effects);
builder.write_f32(self.volume);
builder.write_f32(self.pitch);
if builder.wad_version() >= 14 {
builder.write_resource_id(self.audio_group);
} else {
builder.write_bool32(true); }
builder.write_resource_id_opt(self.audio_file);
builder.write_if_ver(&self.audio_length, "Audio Length", (2024, 6))?;
Ok(())
}
}
#[allow(clippy::bool_to_int_with_if)] fn get_builtin_sound_group_id(gm_version: &GMVersion) -> u32 {
let is_ver = |build| gm_version.is_version_at_least((1, 0, 0, build));
if is_ver(1250) || is_ver(161) && !is_ver(1000) {
0
} else {
1
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AudioType {
Unknown,
Wav,
Ogg,
Mp3,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Flags {
pub embedded: bool,
pub compressed: bool,
}
impl GMElement for Flags {
fn deserialize(reader: &mut DataReader) -> Result<Self> {
let raw = reader.read_u32()?;
Ok(match raw {
100 => Self { embedded: false, compressed: false },
101 => Self { embedded: true, compressed: false },
102 => Self { embedded: false, compressed: true },
103 => Self { embedded: true, compressed: true },
_ => bail!("Invalid/Unknown sound flags {raw}, please report this error"),
})
}
fn serialize(&self, builder: &mut DataBuilder) -> Result<()> {
let mut raw = 100;
if self.embedded {
raw |= 1;
}
if self.compressed {
raw |= 2;
}
builder.write_u32(raw);
Ok(())
}
}