diff --git a/README.md b/README.md index c340e74..0f10d5e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Python library and tool to extract FSB5 (FMOD Sample Bank) files. - MPEG - Vorbis (OGG) -- WAVE (PCM8, PCM16, PCM32) +- WAVE (PCM8, PCM16, PCM32, PCMFLOAT) Other formats can be identified but will be extracted as `.dat` files and may not play as the headers may be missing. diff --git a/fsb5/__init__.py b/fsb5/__init__.py index f326d8b..01347e6 100644 --- a/fsb5/__init__.py +++ b/fsb5/__init__.py @@ -40,7 +40,7 @@ def file_extension(self): @property def is_pcm(self): - return self in (SoundFormat.PCM8, SoundFormat.PCM16, SoundFormat.PCM32) + return self in (SoundFormat.PCM8, SoundFormat.PCM16, SoundFormat.PCM32, SoundFormat.PCMFLOAT) FSB5Header = namedtuple("FSB5Header", [ @@ -211,11 +211,14 @@ def rebuild_sample(self, sample): from . import vorbis return vorbis.rebuild(sample) elif self.header.mode.is_pcm: - from .pcm import rebuild + from .pcm import rebuild, rebuild_float if self.header.mode == SoundFormat.PCM8: width = 1 elif self.header.mode == SoundFormat.PCM16: width = 2 + elif self.header.mode == SoundFormat.PCMFLOAT: + width = 4 + return rebuild_float(sample, width) else: width = 4 return rebuild(sample, width) diff --git a/fsb5/pcm.py b/fsb5/pcm.py index 5d4cd1e..d1dc750 100644 --- a/fsb5/pcm.py +++ b/fsb5/pcm.py @@ -1,4 +1,5 @@ import wave +import struct from io import BytesIO @@ -9,3 +10,36 @@ def rebuild(sample, width): wav.setparams((sample.channels, width, sample.frequency, 0, "NONE", "NONE")) wav.writeframes(data) return ret.getvalue() + +def rebuild_float(sample, width): + data = sample.data[:sample.samples * width] + ret = BytesIO() + with PCMFloatWave_write(ret) as wav: + wav.setparams((sample.channels, width, sample.frequency, 0, "NONE", "NONE")) + wav.writeframes(data) + return ret.getvalue() + +WAVE_FORMAT_IEEE_FLOAT = 3 + +class PCMFloatWave_write(wave.Wave_write): + def _write_header(self, initlength): + assert not self._headerwritten + self._file.write(b'RIFF') + if not self._nframes: + self._nframes = initlength // (self._nchannels * self._sampwidth) + self._datalength = self._nframes * self._nchannels * self._sampwidth + try: + self._form_length_pos = self._file.tell() + except (AttributeError, OSError): + self._form_length_pos = None + self._file.write(struct.pack('