From 466a8fce3c5693fa51cc34ec5d9e718459484e0b Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 25 Mar 2025 03:09:35 +0100 Subject: start on own fmod sound bank impl --- src/fmod.rs | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/fmod.rs (limited to 'src/fmod.rs') diff --git a/src/fmod.rs b/src/fmod.rs new file mode 100644 index 0000000..f0e7b4c --- /dev/null +++ b/src/fmod.rs @@ -0,0 +1,252 @@ +use crate::helper::ReadExt; +use anyhow::{Ok, Result, bail}; +use log::debug; +use std::io::{Cursor, Read, Seek, SeekFrom}; + +pub struct FmodSoundBank { + inner: T, + data_base_offset: u64, + header: FsbHeader, + pub streams: Vec, +} + +pub struct StreamInfo { + header: StreamHeader, +} + +struct FsbHeader { + version: u32, + num_streams: u32, + stream_headers_size: u32, + name_table_size: u32, + data_size: u32, + format: FsbFormat, +} + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum FsbFormat { + None, + Pcm8, + Pcm16, + Pcm24, + Pcm32, + PcmFloat, + GcAdPcm, + ImaAdPcm, + Vag, + HeVag, + Xma, + Mpeg, + Celt, + At9, + Xwma, + Vorbis, + FAdPcm, + Opus, +} + +impl FsbHeader { + pub fn read(mut f: impl Read) -> Result { + let mut buf = [0; 4]; + f.read_exact(&mut buf)?; + if &buf != b"FSB5" { + bail!("invalid magic") + } + + let version = f.read_u32_le()?; + let num_streams = f.read_u32_le()?; + let stream_headers_size = f.read_u32_le()?; + let name_table_size = f.read_u32_le()?; + let data_size = f.read_u32_le()?; + let format = FsbFormat::from_num(f.read_u32_le()?)?; + + debug!("FSB version: {version}"); + debug!("Sample Count: {num_streams}"); + debug!("Format: {format:?}"); + + f.discard(32)?; + + Ok(Self { + data_size, + format, + name_table_size, + num_streams, + stream_headers_size, + version, + }) + } +} + +impl FsbFormat { + pub fn from_num(x: u32) -> Result { + if x < 18 { + Ok(unsafe { std::mem::transmute(x) }) + } else { + bail!("audio format out of range") + } + } +} + +impl FmodSoundBank { + pub fn open(mut file: T) -> Result { + let header = FsbHeader::read(&mut file)?; + let mut streams = Vec::new(); + + for _ in 0..header.num_streams { + let header = StreamHeader::from_packed(file.read_u64_le()?)?; + eprintln!("{header:?}"); + let mut next = header.next_chunk; + while next { + let chunk = ChunkHeader::from_packed(file.read_u32_le()?)?; + + match chunk.kind { + k => todo!("chunk kind {k:?}"), + } + + next = chunk.next_chunk; + } + streams.push(StreamInfo { header }); + } + + if header.name_table_size != 0 { + let mut offsets = Vec::new(); + for _ in 0..header.num_streams { + offsets.push(file.read_u32_le()?); + } + let mut buf = vec![0u8; header.name_table_size as usize]; + file.read_exact(&mut buf)?; + let mut buf = Cursor::new(buf); + let mut names = Vec::new(); + for o in offsets { + buf.seek(SeekFrom::Start(o as u64))?; + names.push(buf.read_cstr()?); + } + } + let data_base_offset = file.stream_position()?; + + Ok(Self { + data_base_offset, + header, + streams, + inner: file, + }) + } + pub fn read_stream(&mut self, n: usize) -> Result<()> { + let info = &self.streams[n]; + + let start = info.header.data_offset; + let end = self + .streams + .get(n + 1) + .map_or(self.header.data_size, |s| s.header.data_offset); + + let mut data = vec![0; (end - start) as usize]; + debug!("{} {}", start, end); + self.inner + .seek(SeekFrom::Start(start as u64 + self.data_base_offset))?; + self.inner.read_exact(&mut data)?; + + Ok(()) + } +} + +#[derive(Debug)] +struct StreamHeader { + next_chunk: bool, + samplerate: u32, + channels: u8, + data_offset: u32, + samples: u32, +} +impl StreamHeader { + const MASKS: &[u64] = &[ + 0b1000000000000000000000000000000000000000000000000000000000000000, + 0b0111100000000000000000000000000000000000000000000000000000000000, + 0b0000011000000000000000000000000000000000000000000000000000000000, + 0b0000000111111111111111111111111111000000000000000000000000000000, + 0b0000000000000000000000000000000000111111111111111111111111111111, + ]; + pub fn from_packed(x: u64) -> Result { + let next_chunk = x & Self::MASKS[0] != 0; + let samplerate_raw = (x & Self::MASKS[1]) >> Self::MASKS[1].trailing_zeros(); + let channels_raw = x & Self::MASKS[2] >> Self::MASKS[2].trailing_zeros(); + let data_offset = (x & Self::MASKS[3]) >> Self::MASKS[3].trailing_zeros(); + let samples = x & Self::MASKS[4]; + Ok(Self { + next_chunk, + samplerate: match samplerate_raw { + 0 => 4000, + 1 => 8000, + 2 => 11000, + 3 => 11025, + 4 => 16000, + 5 => 22050, + 6 => 24000, + 7 => 32000, + 8 => 44100, + 9 => 48000, + 10 => 96000, + x => bail!("invalid sampling frequency: {x}"), + }, + channels: match channels_raw { + 0 => 1, + 1 => 2, + 2 => 4, + 3 => 8, + x => bail!("invalid channel count: {x}"), + }, + data_offset: data_offset as u32 * 16, + samples: samples as u32, + }) + } +} + +struct ChunkHeader { + next_chunk: bool, + size: u32, + kind: ChunkKind, +} +impl ChunkHeader { + const MASKS: &[u32] = &[ + 0b10000000000000000000000000000000, + 0b01111111111111111111111110000000, + 0b00000000000000000000000001111111, + ]; + pub fn from_packed(x: u32) -> Result { + let next_chunk = x & Self::MASKS[0] != 0; + let size = (x & Self::MASKS[1]) >> Self::MASKS[1].trailing_zeros(); + let kind = x & Self::MASKS[2]; + Ok(Self { + kind: ChunkKind::from_num(kind as u8)?, + size, + next_chunk, + }) + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +enum ChunkKind { + Channels, + Samplerate, + Loop, + Comment, + XmaSeekTable, + DspCoefficients, + Atrac9Config, + XwmaConfig, + VorbisSeekTable, + PeakVolume, + VorbisIntraLayers, + OpusDataSize, +} +impl ChunkKind { + pub fn from_num(n: u8) -> Result { + if n < 13 { + Ok(unsafe { std::mem::transmute(n) }) + } else { + bail!("unknown chunk type {n:02x}") + } + } +} -- cgit v1.2.3-70-g09d2