xoreos  0.0.5
wwisesoundbank.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 /* Based on the Wwise BNK specs in the XentaxWiki:
26  * <http://wiki.xentax.com/index.php/Wwise_SoundBank_(*.bnk)>
27  */
28 
29 #include <cassert>
30 
31 #include "src/common/error.h"
32 #include "src/common/util.h"
33 #include "src/common/debug.h"
34 #include "src/common/strutil.h"
35 #include "src/common/encoding.h"
36 
37 #include "src/aurora/resman.h"
38 
40 #include "src/sound/audiostream.h"
41 
43 
44 namespace Sound {
45 
47  _dataOffset(SIZE_MAX) {
48 
49  assert(_bnk);
50 
51  load(*_bnk);
52 }
53 
54 WwiseSoundBank::WwiseSoundBank(const Common::UString &name) : _bankID(0), _dataOffset(SIZE_MAX) {
55  _bnk.reset(ResMan.getResource(name, Aurora::kFileTypeBNK));
56  if (!_bnk)
57  throw Common::Exception("No such BNK resource \"%s\"", name.c_str());
58 
59  load(*_bnk);
60 }
61 
62 WwiseSoundBank::WwiseSoundBank(uint64 hash) : _bankID(0), _dataOffset(SIZE_MAX) {
63  _bnk.reset(ResMan.getResource(hash));
64  if (!_bnk)
65  throw Common::Exception("No such BNK resource \"%s\"", Common::formatHash(hash).c_str());
66 
67  load(*_bnk);
68 }
69 
71  return _files.size();
72 }
73 
75  return _sounds.size();
76 }
77 
79  if (index >= _files.size())
80  throw Common::Exception("WwiseSoundBank::getFileStruct(): Index out of range (%s >= %s)",
82  Common::composeString(_files.size()).c_str());
83 
84  return _files[index];
85 }
86 
88  if (index >= _sounds.size())
89  throw Common::Exception("WwiseSoundBank::getSoundStruct(): Index out of range (%s >= %s)",
91  Common::composeString(_sounds.size()).c_str());
92 
93  return _sounds[index];
94 }
95 
96 uint32 WwiseSoundBank::getFileID(size_t index) const {
97  return getFileStruct(index).id;
98 }
99 
100 uint32 WwiseSoundBank::getSoundID(size_t index) const {
101  return getSoundStruct(index).id;
102 }
103 
105  return getSoundStruct(index).fileID;
106 }
107 
109  std::map<uint32, size_t>::const_iterator index = _fileIDs.find(id);
110  if (index == _fileIDs.end())
111  return SIZE_MAX;
112 
113  return index->second;
114 }
115 
117  std::map<uint32, size_t>::const_iterator index = _soundIDs.find(id);
118  if (index == _soundIDs.end())
119  return SIZE_MAX;
120 
121  return index->second;
122 }
123 
125  if (isEmptyFile(index))
126  return new EmptyAudioStream;
127 
129  return makeWwRIFFVorbisStream(wwData.release(), true);
130 }
131 
133  if (isEmptySound(index))
134  return new EmptyAudioStream;
135 
137  return makeWwRIFFVorbisStream(wwData.release(), true);
138 }
139 
140 bool WwiseSoundBank::isEmptyFile(size_t index) const {
141  return getFileStruct(index).size == 0;
142 }
143 
144 bool WwiseSoundBank::isEmptySound(size_t index) const {
145  const Sound &sound = getSoundStruct(index);
146 
147  return sound.isEmbedded && (sound.fileSize == 0);
148 }
149 
151  const File &file = getFileStruct(index);
152 
153  if (_dataOffset == SIZE_MAX)
154  throw Common::Exception("WwiseSoundBank::getFileData(): No data offset");
155 
156  _bnk->seek(_dataOffset + file.offset);
157 
158  return _bnk->readStream(file.size);
159 }
160 
162  const Sound &sound = getSoundStruct(index);
163 
164  if (!sound.isEmbedded) {
165  // Streaming => loose OGG file
166 
168  ResMan.getResource(Common::composeString(sound.fileID), Aurora::kFileTypeOGG);
169 
170  if (!data)
171  throw Common::Exception("WwiseSoundBank::getSoundData(): No such OGG file (%s, %u, %u)",
172  Common::composeString(index).c_str(), sound.id, sound.fileID);
173 
174  return data;
175  }
176 
177  if (sound.fileSource == _bankID) {
178  // Sound file is embedded in this bank
179 
180  _bnk->seek(sound.fileOffset);
181 
182  return _bnk->readStream(sound.fileSize);
183  }
184 
185  // Sound file is embedded in another bank
186 
187  std::map<uint32, Common::UString>::const_iterator bankName = _banks.find(sound.fileSource);
188  if (bankName == _banks.end())
189  throw Common::Exception("WwiseSoundBank::getSoundData(): Externally embedded file (%s, %u, %u, %u) "
190  "without a bank name", Common::composeString(index).c_str(),
191  sound.id, sound.fileID, sound.fileSource);
192 
194  if (!bank)
195  throw Common::Exception("WwiseSoundBank::getSoundData(): Bank \"%s\" for externally embedded file "
196  "(%s, %u, %u) does not exist", bankName->second.c_str(),
197  Common::composeString(index).c_str(), sound.id, sound.fileID);
198 
199  bank->seek(sound.fileOffset);
200 
201  return bank->readStream(sound.fileSize);
202 }
203 
205  kSectionBankHeader = MKTAG('B', 'K', 'H', 'D'),
206  kSectionDataIndex = MKTAG('D', 'I', 'D', 'X'),
207  kSectionData = MKTAG('D', 'A', 'T', 'A'),
208  kSectionObjects = MKTAG('H', 'I', 'R', 'C'),
209  kSectionSoundTypeID = MKTAG('S', 'T', 'I', 'D')
210 };
211 
232 };
233 
235  bnk.seek(0);
236  const uint32 id = bnk.readUint32BE();
237  if (id != kSectionBankHeader)
238  throw Common::Exception("Not a BNK file (%s)", Common::debugTag(id).c_str());
239 
240  debugC(Common::kDebugSound, 3, ".---");
241 
242  bnk.seek(0);
243  while (!bnk.eos() && (bnk.pos() != bnk.size())) {
244  const uint32 sectionType = bnk.readUint32BE();
245  const size_t sectionSize = bnk.readUint32LE();
246  const size_t sectionStart = bnk.pos();
247  const size_t sectionEnd = sectionStart + sectionSize;
248 
249  debugC(Common::kDebugSound, 3, "- Section \"%s\" (%s)", Common::debugTag(sectionType).c_str(),
250  Common::composeString(sectionSize).c_str());
251 
252  switch (sectionType) {
253  case kSectionBankHeader: {
254  const uint32 version = bnk.readUint32LE();
255  _bankID = bnk.readUint32LE();
256 
257  if (version != 48)
258  throw Common::Exception("WwiseSoundBank::load(): Unsupported BNK version %u", version);
259 
260  debugC(Common::kDebugSound, 3, " - Version: %u", version);
261  debugC(Common::kDebugSound, 3, " - Bank ID: %u", _bankID);
262  break;
263  }
264 
265  case kSectionDataIndex: {
266  if ((sectionSize % 12) != 0)
267  throw Common::Exception("WwiseSoundBank::load(): Unaligned data index");
268 
269  debugC(Common::kDebugSound, 3, " - %s entries", Common::composeString(sectionSize / 12).c_str());
270 
271  _files.resize(sectionSize / 12);
272  for (std::vector<File>::iterator f = _files.begin(); f != _files.end(); ++f) {
273  f->id = bnk.readUint32LE();
274  f->offset = bnk.readUint32LE();
275  f->size = bnk.readUint32LE();
276 
277  _fileIDs.insert(std::make_pair(f->id, std::distance(_files.begin(), f)));
278 
279  debugC(Common::kDebugSound, 3, " - %u | %s, %s", f->id,
280  Common::composeString(f->offset).c_str(),
281  Common::composeString(f->size).c_str());
282  }
283  break;
284  }
285 
286  case kSectionData:
287  _dataOffset = sectionStart;
288  debugC(Common::kDebugSound, 3, "DATAOFFSET %s", Common::composeString(_dataOffset).c_str());
289  break;
290 
291  case kSectionObjects: {
292  const size_t count = bnk.readUint32LE();
293  for (size_t i = 0; i < count; i++) {
294  const uint32 type = bnk.readUint32LE();
295  const size_t size = bnk.readUint32LE();
296  const size_t start = bnk.pos();
297  const size_t end = start + size;
298 
299  const uint32 objectID = bnk.readUint32LE();
300 
301  debugC(Common::kDebugSound, 3, " - %s/%s: %u, %u (%s)", Common::composeString(i).c_str(),
302  Common::composeString(count).c_str(), type, objectID,
303  Common::composeString(size).c_str());
304 
305  if (type == kObjectSound) {
306  _sounds.push_back(Sound());
307  Sound &sound = _sounds.back();
308 
309  sound.id = objectID;
310 
311  bnk.skip(4); // Unknown
312  const uint32 embedded = bnk.readUint32LE();
313 
314  sound.isEmbedded = embedded == 0;
315  sound.zeroLatency = embedded == 2;
316 
317  sound.fileID = bnk.readUint32LE();
318  sound.fileSource = bnk.readUint32LE();
319 
320  sound.fileOffset = sound.fileSize = SIZE_MAX;
321  if (sound.isEmbedded) {
322  sound.fileOffset = bnk.readUint32LE();
323  sound.fileSize = bnk.readUint32LE();
324  }
325 
326  sound.type = static_cast<SoundType>(bnk.readByte());
327 
328  _soundIDs.insert(std::make_pair(sound.id, _sounds.size() - 1));
329 
330  debugC(Common::kDebugSound, 3, "=> SOUND: %u | %u, %u | %u (%s, %s)",
331  embedded, sound.fileID, sound.fileSource, sound.type,
332  Common::composeString(sound.fileOffset).c_str(),
333  Common::composeString(sound.fileSize).c_str());
334 
335  } else if (type == kObjectMusicTrack) {
336  _sounds.push_back(Sound());
337  Sound &music = _sounds.back();
338 
339  music.id = objectID;
340 
341  bnk.skip(8); // Unknown
342 
343  const uint32 embedded = bnk.readUint32LE();
344 
345  music.isEmbedded = embedded == 0;
346  music.zeroLatency = embedded == 2;
347 
348  music.fileID = bnk.readUint32LE();
349  music.fileSource = bnk.readUint32LE();
350 
351  music.fileOffset = music.fileSize = SIZE_MAX;
352  if (music.isEmbedded) {
353  music.fileOffset = bnk.readUint32LE();
354  music.fileSize = bnk.readUint32LE();
355  }
356 
357  music.type = kSoundTypeMusic;
358 
359  _soundIDs.insert(std::make_pair(music.id, _sounds.size() - 1));
360 
361  debugC(Common::kDebugSound, 3, "=> MUSIC: %u | %u, %u | %u (%s, %s)",
362  embedded, music.fileID, music.fileSource, music.type,
363  Common::composeString(music.fileOffset).c_str(),
364  Common::composeString(music.fileSize).c_str());
365  }
366 
367  bnk.seek(end);
368  }
369  break;
370  }
371 
372  case kSectionSoundTypeID: {
373  bnk.skip(4); // Unknown
374  const size_t count = bnk.readUint32LE();
375  for (size_t i = 0; i < count; i++) {
376  const uint32 bankID = bnk.readUint32LE();
377  const uint8 bankNameLength = bnk.readByte();
378 
379  _banks[bankID] = Common::readStringFixed(bnk, Common::kEncodingASCII, bankNameLength);
380  debugC(Common::kDebugSound, 3, "~> %u, \"%s\"", bankID, _banks[bankID].c_str());
381  }
382 
383  break;
384  };
385 
386  default:
387  break;
388  }
389 
390  bnk.seek(sectionEnd);
391  }
392 
393  debugC(Common::kDebugSound, 3, "'---");
394 }
395 
396 } // End of namespace Sound
#define ResMan
Shortcut for accessing the sound manager.
Definition: resman.h:557
#define MKTAG(a0, a1, a2, a3)
A wrapper macro used around four character constants, like &#39;DATA&#39;, to ensure portability.
Definition: endianness.h:140
"GSound", global, non-engine sound.
Definition: debugman.h:43
uint32 readUint32LE()
Read an unsigned 32-bit word stored in little endian (LSB first) order from the stream and return it...
Definition: readstream.h:133
void debugC(Common::DebugChannel channel, uint32 level, const char *s,...)
Definition: debug.cpp:34
A class holding an UTF-8 string.
Definition: ustring.h:48
virtual size_t seek(ptrdiff_t offset, Origin whence=kOriginBegin)=0
Sets the stream position indicator for the stream.
std::map< uint32, Common::UString > _banks
void reset(PointerType o=0)
Resets the pointer with the new value.
Definition: scopedptr.h:87
Utility functions for debug output.
const File & getFileStruct(size_t index) const
PointerType release()
Returns the plain pointer value and releases ScopedPtr.
Definition: scopedptr.h:103
UString composeString(T value)
Convert any POD integer, float/double or bool type into a string.
Definition: strutil.cpp:276
uint64_t uint64
Definition: types.h:206
uint8_t uint8
Definition: types.h:200
virtual bool eos() const =0
Returns true if a read failed because the stream has been reached.
size_t offset
Offset of the file from the beginning of the data section.
Common::ScopedPtr< Common::SeekableReadStream > _bnk
size_t findFileByID(uint32 id) const
Return the index of a file from its ID, or SIZE_MAX if not found.
Common::SeekableReadStream * getFileData(size_t index) const
RewindableAudioStream * getFile(size_t index) const
Utility templates and functions for working with strings and streams.
Sound::RewindableAudioStream * makeWwRIFFVorbisStream(Common::SeekableReadStream *wwRIFFVorbis, bool disposeAfterUse)
Exception that provides a stack of explanations.
Definition: error.h:36
std::map< uint32, size_t > _fileIDs
std::vector< Sound > _sounds
static UString formatHash(uint64 hash)
Definition: hash.h:261
uint32 getSoundID(size_t index) const
Return the ID of a referenced sound.
Definition: game.h:37
size_t findSoundByID(uint32 id) const
Return the index of a sound from its ID, or SIZE_MAX if not found.
Basic exceptions to throw.
A rewindable audio stream.
Definition: audiostream.h:125
const char * c_str() const
Return the (utf8 encoded) string data.
Definition: ustring.cpp:249
Utility templates and functions.
Common::SeekableReadStream * getSoundData(size_t index) const
void load(Common::SeekableReadStream &bnk)
virtual size_t skip(ptrdiff_t offset)
Skip the specified number of bytes, adding that offset to the current position in the stream...
Definition: readstream.h:317
uint32 getSoundFileID(size_t index) const
Return the ID of a file used by a referenced sound.
Audio, Ogg Vorbis.
Definition: types.h:144
uint32 getFileID(size_t index) const
Return the ID of an embedded file.
Utility functions for working with differing string encodings.
StackException Exception
Definition: error.h:59
WwiseSoundBank(Common::SeekableReadStream *bnk)
virtual size_t size() const =0
Obtains the total size of the stream, measured in bytes.
virtual size_t pos() const =0
Obtains the current value of the stream position indicator of the stream.
std::map< uint32, size_t > _soundIDs
uint32 readUint32BE()
Read an unsigned 32-bit word stored in big endian (MSB first) order from the stream and return it...
Definition: readstream.h:166
Plain, unextended ASCII (7bit clean).
Definition: encoding.h:40
size_t size
Size of the file in bytes.
bool isEmptyFile(size_t index) const
bool isEmptySound(size_t index) const
An empty audio stream that plays nothing.
Definition: audiostream.h:155
uint32_t uint32
Definition: types.h:204
UString debugTag(uint32 tag, bool trim)
Create an elaborate string from an integer tag, for debugging purposes.
Definition: strutil.cpp:117
A Wwise SoundBank, found in Dragon Age II as BNK files.
#define SIZE_MAX
Definition: types.h:172
std::vector< File > _files
const Sound & getSoundStruct(size_t index) const
UString readStringFixed(SeekableReadStream &stream, Encoding encoding, size_t length)
Read length bytes as a string with the given encoding out of a stream.
Definition: encoding.cpp:297
size_t getFileCount() const
Return the number of embedded files.
size_t getSoundCount() const
Return the number of sounds this bank references.
RewindableAudioStream * getSound(size_t index) const
Interface for a seekable & readable data stream.
Definition: readstream.h:265
Streaming audio.
An embedded sound file within the SoundBank.
byte readByte()
Read an unsigned byte from the stream and return it.
Definition: readstream.h:92
The global resource manager for Aurora resources.