188 lines
4.6 KiB
Rust
188 lines
4.6 KiB
Rust
//! Marathon Sounds format handling.
|
|
|
|
use crate::durandal::{bin::*, err::*, fixed::*, sound::*};
|
|
use bitflags::bitflags;
|
|
use std::collections::BTreeMap;
|
|
|
|
/// Reads a sound.
|
|
pub fn read_sound(b: &[u8]) -> ResultS<Sound16>
|
|
{
|
|
read_data! {
|
|
endian: BIG, buf: b, size: 21, start: 0, data {
|
|
let len = u32[4] usize;
|
|
let rate = u16[8];
|
|
let lp_beg = u32[12] usize;
|
|
let lp_end = u32[16] usize;
|
|
let magic = u8[20];
|
|
}
|
|
}
|
|
|
|
match magic {
|
|
0 => {
|
|
let stream = &b[22..22 + len];
|
|
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
|
|
}
|
|
0xFF => {
|
|
read_data! {
|
|
endian: BIG, buf: b, size: 42, start: 22, data {
|
|
let len = u32[0] usize;
|
|
let bps = u16[26];
|
|
}
|
|
}
|
|
|
|
match bps {
|
|
16 => {
|
|
let stream = &b[63..63 + len * 2];
|
|
Ok(Sound16::new_from_16(rate, lp_beg, lp_end, stream))
|
|
}
|
|
8 => {
|
|
let stream = &b[63..63 + len];
|
|
Ok(Sound16::new_from_8(rate, lp_beg, lp_end, stream))
|
|
}
|
|
_ => bail!("bad bits per sample"),
|
|
}
|
|
}
|
|
_ => bail!("invalid magic number"),
|
|
}
|
|
}
|
|
|
|
/// Reads a sound definition.
|
|
pub fn read_sound_def(b: &[u8]) -> ResultS<Option<(Vec<usize>, u16, SoundDef)>>
|
|
{
|
|
read_data! {
|
|
endian: BIG, buf: b, size: 64, start: 0, data {
|
|
let index = u16[0];
|
|
let volume = u16[2] enum Volume;
|
|
let flags = u16[4] flag SoundFlags;
|
|
let chance = u16[6];
|
|
let pitch_lo = Fixed[8];
|
|
let pitch_hi = Fixed[12];
|
|
let n_sounds = u16[16] usize;
|
|
let grp_ofs = u32[20] usize;
|
|
}
|
|
}
|
|
|
|
if index == u16::max_value() {
|
|
return Ok(None);
|
|
}
|
|
|
|
if n_sounds > 5 {
|
|
bail!("too many sounds");
|
|
}
|
|
|
|
let mut ofs = Vec::with_capacity(n_sounds);
|
|
let mut p = 36;
|
|
|
|
for _ in 0..n_sounds {
|
|
ofs.push(grp_ofs + usize_from_u32(u32b(&b[p..])));
|
|
p += 4;
|
|
}
|
|
|
|
let sounds = Vec::with_capacity(n_sounds);
|
|
|
|
Ok(Some((ofs, index,
|
|
SoundDef{volume, flags, chance, pitch_lo, pitch_hi, sounds})))
|
|
}
|
|
|
|
/// Reads all sounds from a Sound file.
|
|
pub fn read_sounds(b: &[u8]) -> ResultS<Vec<SoundTable>>
|
|
{
|
|
read_data! {
|
|
endian: BIG, buf: b, size: 260, start: 0, data {
|
|
let version = u32[0];
|
|
let magic = Ident[4];
|
|
let src_num = u16[8] usize;
|
|
let snd_num = u16[10] usize;
|
|
}
|
|
}
|
|
|
|
if version != 1 || magic != b"snd2" {
|
|
bail!("bad sound header");
|
|
}
|
|
|
|
let mut sc = Vec::with_capacity(src_num);
|
|
let mut p = 260;
|
|
|
|
for _ in 0..src_num {
|
|
let mut st = BTreeMap::new();
|
|
|
|
for _ in 0..snd_num {
|
|
if let Some((ofs, idx, mut def)) = read_sound_def(&b[p..p + 64])? {
|
|
for &ofs in &ofs {
|
|
def.sounds.push(read_sound(&b[ofs..])?);
|
|
}
|
|
|
|
st.insert(idx, def);
|
|
}
|
|
|
|
p += 64;
|
|
}
|
|
|
|
sc.push(st);
|
|
}
|
|
|
|
Ok(sc)
|
|
}
|
|
|
|
/// A sound definition containing one, many or no sounds.
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
pub struct SoundDef
|
|
{
|
|
/// The volume type for this sound.
|
|
pub volume: Volume,
|
|
|
|
/// The flags for this sound.
|
|
pub flags: SoundFlags,
|
|
|
|
/// The chance out of `u16::max_value()` that this sound will not play.
|
|
pub chance: u16,
|
|
|
|
/// The low random pitch bound.
|
|
pub pitch_lo: Fixed,
|
|
|
|
/// The high random pitch bound.
|
|
pub pitch_hi: Fixed,
|
|
|
|
/// All of the sounds in this collection.
|
|
pub sounds: Vec<Sound16>,
|
|
}
|
|
|
|
/// A table of sound definitions.
|
|
pub type SoundTable = BTreeMap<u16, SoundDef>;
|
|
|
|
bitflags! {
|
|
/// Flags for `SoundDef`.
|
|
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
|
|
pub struct SoundFlags: u16
|
|
{
|
|
/// The sound will not restart when trying to play over itself.
|
|
const NO_RESTART = 1;
|
|
/// The sound will not switch channels when trying to play over itself.
|
|
const NO_CHANNEL_SWITCH = 1 << 1;
|
|
/// The pitch variance will be halved.
|
|
const LESS_PITCH_CHANGE = 1 << 2;
|
|
/// The pitch variance will be nullified.
|
|
const NO_PITCH_CHANGE = 1 << 3;
|
|
/// The sound will play even when completely obstructed by walls.
|
|
const NO_OBSTRUCTION = 1 << 4;
|
|
/// The sound will play even when completely obstructed by media.
|
|
const NO_MEDIA_OBSTRUCT = 1 << 5;
|
|
/// The sound will have special stereo effects.
|
|
const AMBIENT = 1 << 6;
|
|
}
|
|
}
|
|
|
|
c_enum! {
|
|
/// The type of volume this sound has.
|
|
#[cfg_attr(feature = "serde_obj", derive(serde::Serialize))]
|
|
#[derive(Debug)]
|
|
pub enum Volume: u16
|
|
{
|
|
Quiet = 0,
|
|
Normal = 1,
|
|
Loud = 2,
|
|
}
|
|
}
|
|
|
|
// EOF
|