-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SOUND: Add support for FMOD SampleBanks
Used by Dragon Age: Origins. Heavily based on Luigi Auriemma's fsbext tool, released under the terms of the GPLv2+.
- Loading branch information
Showing
3 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
/* xoreos - A reimplementation of BioWare's Aurora engine | ||
* | ||
* xoreos is the legal property of its developers, whose names | ||
* can be found in the AUTHORS file distributed with this source | ||
* distribution. | ||
* | ||
* xoreos is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public License | ||
* as published by the Free Software Foundation; either version 3 | ||
* of the License, or (at your option) any later version. | ||
* | ||
* xoreos is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with xoreos. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
/** @file | ||
* An FMOD SampleBank, found in Dragon Age: Origins as FSB files. | ||
*/ | ||
|
||
/* Based heavily on Luigi Auriemma's fsbext tool | ||
* (<http://aluigi.altervista.org/papers.htm#others-file>), which is licensed | ||
* under the terms of the GPLv2. | ||
* | ||
* The original copyright note in fsbext reads as follows: | ||
* | ||
* Copyright 2005-2012 Luigi Auriemma | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with this program; if not, write to the Free Software | ||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
* | ||
* http://www.gnu.org/licenses/gpl-2.0.txt | ||
*/ | ||
|
||
#include <cassert> | ||
|
||
#include "src/common/error.h" | ||
#include "src/common/util.h" | ||
#include "src/common/strutil.h" | ||
#include "src/common/encoding.h" | ||
|
||
#include "src/aurora/resman.h" | ||
|
||
#include "src/sound/fmodsamplebank.h" | ||
|
||
#include "src/sound/decoders/mp3.h" | ||
#include "src/sound/decoders/adpcm.h" | ||
|
||
namespace Sound { | ||
|
||
FMODSampleBank::FMODSampleBank(Common::SeekableReadStream *fsb) : _fsb(fsb) { | ||
assert(_fsb); | ||
|
||
load(*_fsb); | ||
} | ||
|
||
FMODSampleBank::FMODSampleBank(const Common::UString &name) { | ||
_fsb.reset(ResMan.getResource(name, Aurora::kFileTypeFSB)); | ||
if (!_fsb) | ||
throw Common::Exception("No such FSB resource \"%s\"", name.c_str()); | ||
|
||
load(*_fsb); | ||
} | ||
|
||
size_t FMODSampleBank::getSampleCount() const { | ||
return _samples.size(); | ||
} | ||
|
||
const Common::UString &FMODSampleBank::getSampleName(size_t index) const { | ||
if (index >= _samples.size()) | ||
throw Common::Exception("FMODSampleBank::getSampleName(): Index out of range (%s >= %s)", | ||
Common::composeString(index).c_str(), | ||
Common::composeString(_samples.size()).c_str()); | ||
|
||
return _samples[index].name; | ||
} | ||
|
||
bool FMODSampleBank::hasSample(const Common::UString &name) const { | ||
return _sampleMap.find(name) != _sampleMap.end(); | ||
} | ||
|
||
enum SampleFlags { | ||
kSampleFlagMP3 = 0x00000200, | ||
kSampleFlagIMAADPCM = 0x00400000 | ||
}; | ||
|
||
RewindableAudioStream *FMODSampleBank::getSample(const Sample &sample) const { | ||
_fsb->seek(sample.offset); | ||
Common::ScopedPtr<Common::SeekableReadStream> dataStream(_fsb->readStream(sample.size)); | ||
|
||
if (sample.flags & kSampleFlagMP3) { | ||
warning("MP3"); | ||
return makeMP3Stream(dataStream.release(), true); | ||
} | ||
|
||
if (sample.flags & kSampleFlagIMAADPCM) { | ||
warning("APCM"); | ||
return makeADPCMStream(dataStream.release(), true, dataStream->size(), | ||
kADPCMMSIma, sample.defFreq, sample.channels, 36 * sample.channels); | ||
} | ||
|
||
throw Common::Exception("FMODSampleBank::getSample(): Unknown format (0x%08X)", sample.flags); | ||
} | ||
|
||
RewindableAudioStream *FMODSampleBank::getSample(size_t index) const { | ||
if (index >= _samples.size()) | ||
throw Common::Exception("FMODSampleBank::getSampleName(): Index out of range (%s >= %s)", | ||
Common::composeString(index).c_str(), | ||
Common::composeString(_samples.size()).c_str()); | ||
|
||
return getSample(_samples[index]); | ||
} | ||
|
||
RewindableAudioStream *FMODSampleBank::getSample(const Common::UString &name) const { | ||
std::map<Common::UString, const Sample *>::const_iterator s = _sampleMap.find(name); | ||
if (s == _sampleMap.end()) | ||
throw Common::Exception("FMODSampleBank::getSampleName(): No such sample \"%s\"", name.c_str()); | ||
|
||
return getSample(*s->second); | ||
} | ||
|
||
enum HeaderFlags { | ||
kHeaderFlagSimpleInfo = 0x00000002 | ||
}; | ||
|
||
void FMODSampleBank::load(Common::SeekableReadStream &fsb) { | ||
static const uint32 kFSBID = MKTAG('F', 'S', 'B', '4'); | ||
|
||
const uint32 id = fsb.readUint32BE(); | ||
if (id != kFSBID) | ||
throw Common::Exception("Not a FSB file (%s)", Common::debugTag(id).c_str()); | ||
|
||
const size_t sampleCount = fsb.readUint32LE(); | ||
|
||
const size_t sampleInfoSize = fsb.readUint32LE(); | ||
fsb.skip(4); // sampleDataSize | ||
|
||
fsb.skip(4); // version | ||
const uint32 flags = fsb.readUint32LE(); | ||
|
||
fsb.skip(24); // Unknown | ||
|
||
const size_t offsetInfo = 48; | ||
size_t offsetData = offsetInfo + sampleInfoSize; | ||
|
||
fsb.seek(offsetInfo); | ||
|
||
_samples.resize(sampleCount); | ||
for (std::vector<Sample>::iterator s = _samples.begin(); s != _samples.end(); ++s) { | ||
const bool isSimple = (flags & kHeaderFlagSimpleInfo) && (s != _samples.begin()); | ||
|
||
if (isSimple) { | ||
*s = _samples[0]; | ||
|
||
s->name.clear(); | ||
|
||
s->length = fsb.readUint32LE(); | ||
s->size = fsb.readUint32LE(); | ||
|
||
} else { | ||
const size_t infoSize = fsb.readUint16LE(); | ||
|
||
if (infoSize < 80) | ||
throw Common::Exception("FMODSampleBank::load(): Invalid sample info size %s", | ||
Common::composeString(infoSize).c_str()); | ||
|
||
s->name = Common::readStringFixed(fsb, Common::kEncodingASCII, 30); | ||
|
||
s->length = fsb.readUint32LE(); | ||
s->size = fsb.readUint32LE(); | ||
|
||
s->loopStart = fsb.readUint32LE(); | ||
s->loopEnd = fsb.readUint32LE(); | ||
|
||
s->flags = fsb.readUint32LE(); | ||
|
||
s->defFreq = fsb.readSint32LE(); | ||
s->defVol = fsb.readUint16LE(); | ||
s->defPan = fsb.readSint16LE(); | ||
s->defPri = fsb.readUint16LE(); | ||
|
||
s->channels = fsb.readUint16LE(); | ||
|
||
s->minDistance = fsb.readIEEEFloatLE(); | ||
s->maxDistance = fsb.readIEEEFloatLE(); | ||
|
||
s->varFreq = fsb.readSint32LE(); | ||
s->varVol = fsb.readUint16LE(); | ||
s->varPan = fsb.readSint16LE(); | ||
|
||
fsb.skip(infoSize - 80); | ||
} | ||
|
||
s->offset = offsetData; | ||
offsetData += s->size; | ||
|
||
if (!s->name.empty()) | ||
_sampleMap.insert(std::make_pair(s->name, &*s)); | ||
} | ||
} | ||
|
||
} // End of namespace Sound |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* xoreos - A reimplementation of BioWare's Aurora engine | ||
* | ||
* xoreos is the legal property of its developers, whose names | ||
* can be found in the AUTHORS file distributed with this source | ||
* distribution. | ||
* | ||
* xoreos is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU General Public License | ||
* as published by the Free Software Foundation; either version 3 | ||
* of the License, or (at your option) any later version. | ||
* | ||
* xoreos is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU General Public License | ||
* along with xoreos. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
/** @file | ||
* An FMOD SampleBank, found in Dragon Age: Origins as FSB files. | ||
*/ | ||
|
||
#ifndef SOUND_FMODSAMPLEBANK_H | ||
#define SOUND_FMODSAMPLEBANK_H | ||
|
||
#include "src/common/ustring.h" | ||
#include "src/common/scopedptr.h" | ||
#include "src/common/readstream.h" | ||
|
||
namespace Sound { | ||
|
||
class RewindableAudioStream; | ||
|
||
/** Class to hold audio resource data of an FMOD samplebank file. | ||
* | ||
* An FSB file is a samplebank, i.e. an archive containing one or more | ||
* audio files. It's part of FMOD sound middleware, and commonly used | ||
* together with FEV files, which define events and effect chains. | ||
* | ||
* FSB files are found in Dragon Age: Origins. | ||
* | ||
* Only version 4 of the FSB format is supported, because that's the | ||
* version used by Dragon Age: Origins. | ||
*/ | ||
class FMODSampleBank { | ||
public: | ||
FMODSampleBank(Common::SeekableReadStream *fsb); | ||
FMODSampleBank(const Common::UString &name); | ||
~FMODSampleBank() { } | ||
|
||
/** Return the number of sample files. */ | ||
size_t getSampleCount() const; | ||
|
||
/** Return the name of a sample. */ | ||
const Common::UString &getSampleName(size_t index) const; | ||
/** Does a sample with this name exist in the sample bank? */ | ||
bool hasSample(const Common::UString &name) const; | ||
|
||
/** Return the audio stream of a sample, by index. */ | ||
RewindableAudioStream *getSample(size_t index) const; | ||
/** Return the audio stream of a sample, by name. */ | ||
RewindableAudioStream *getSample(const Common::UString &name) const; | ||
|
||
private: | ||
struct Sample { | ||
Common::UString name; ///< Name of the sample. | ||
|
||
uint32 length; ///< Length of the sample, in audio samples. | ||
|
||
uint32 loopStart; | ||
uint32 loopEnd; | ||
|
||
uint32 flags; | ||
|
||
int32 defFreq; | ||
uint16 defVol; | ||
int16 defPan; | ||
uint16 defPri; | ||
|
||
uint16 channels; | ||
|
||
float minDistance; | ||
float maxDistance; | ||
|
||
int32 varFreq; | ||
uint16 varVol; | ||
int16 varPan; | ||
|
||
size_t offset; ///< Offset to the sample within the FSB. | ||
size_t size; ///< Size of the sample in bytes. | ||
}; | ||
|
||
|
||
Common::ScopedPtr<Common::SeekableReadStream> _fsb; | ||
|
||
std::vector<Sample> _samples; | ||
|
||
std::map<Common::UString, const Sample *> _sampleMap; | ||
|
||
|
||
void load(Common::SeekableReadStream &fsb); | ||
|
||
RewindableAudioStream *getSample(const Sample &sample) const; | ||
}; | ||
|
||
} // End of namespace Sound | ||
|
||
#endif // SOUND_FMODSAMPLEBANK_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters