private WaveBank( AudioEngine audioEngine, string waveBankFilename, bool streaming, int offset, int packetsize) { if (audioEngine == null) { throw new ArgumentNullException(nameof(audioEngine)); } if (string.IsNullOrEmpty(waveBankFilename)) { throw new ArgumentNullException(nameof(waveBankFilename)); } // Is this a streaming wavebank? if (streaming) { if (offset != 0) { throw new ArgumentException( "We only support a zero offset in streaming banks.", nameof(offset)); } if (packetsize < 2) { throw new ArgumentException( "The packet size must be greater than 2.", nameof(packetsize)); } _streaming = true; _offset = offset; _packetSize = packetsize; } //XWB PARSING //Adapted from MonoXNA //Originally adaped from Luigi Auriemma's unxwb WaveBankHeader wavebankheader; WaveBankData wavebankdata; wavebankdata.EntryNameElementSize = 0; wavebankdata.CompactFormat = 0; wavebankdata.Alignment = 0; wavebankdata.BuildTime = 0; int wavebank_offset = 0; _waveBankFileName = waveBankFilename; using (var reader = new BinaryReader(AudioEngine.OpenStream(waveBankFilename))) { reader.ReadBytes(4); _version = wavebankheader.Version = reader.ReadInt32(); int last_segment = 4; //if (wavebankheader.Version == 1) goto WAVEBANKDATA; if (wavebankheader.Version <= 3) { last_segment = 3; } if (wavebankheader.Version >= 42) { reader.ReadInt32(); // skip HeaderVersion } wavebankheader.Segments = new Segment[5]; for (int i = 0; i <= last_segment; i++) { wavebankheader.Segments[i].Offset = reader.ReadInt32(); wavebankheader.Segments[i].Length = reader.ReadInt32(); } reader.BaseStream.Seek(wavebankheader.Segments[0].Offset, SeekOrigin.Begin); //WAVEBANKDATA: wavebankdata.Flags = reader.ReadInt32(); wavebankdata.EntryCount = reader.ReadInt32(); if ((wavebankheader.Version == 2) || (wavebankheader.Version == 3)) { wavebankdata.BankName = System.Text.Encoding.UTF8.GetString( reader.ReadBytes(16), 0, 16).Replace("\0", "", StringComparison.InvariantCulture); } else { wavebankdata.BankName = System.Text.Encoding.UTF8.GetString( reader.ReadBytes(64), 0, 64).Replace("\0", "", StringComparison.InvariantCulture); } _bankName = wavebankdata.BankName; if (wavebankheader.Version == 1) { //wavebank_offset = (int)ftell(fd) - file_offset; wavebankdata.EntryMetaDataElementSize = 20; } else { wavebankdata.EntryMetaDataElementSize = reader.ReadInt32(); wavebankdata.EntryNameElementSize = reader.ReadInt32(); wavebankdata.Alignment = reader.ReadInt32(); wavebank_offset = wavebankheader.Segments[1].Offset; //METADATASEGMENT } if ((wavebankdata.Flags & Flag_Compact) != 0) { reader.ReadInt32(); // compact_format } _playRegionOffset = wavebankheader.Segments[last_segment].Offset; if (_playRegionOffset == 0) { _playRegionOffset = wavebank_offset + (wavebankdata.EntryCount * wavebankdata.EntryMetaDataElementSize); } int segidx_entry_name = 2; if (wavebankheader.Version >= 42) { segidx_entry_name = 3; } if ((wavebankheader.Segments[segidx_entry_name].Offset != 0) && (wavebankheader.Segments[segidx_entry_name].Length != 0)) { if (wavebankdata.EntryNameElementSize == -1) { wavebankdata.EntryNameElementSize = 0; } byte[] entry_name = new byte[wavebankdata.EntryNameElementSize + 1]; entry_name[wavebankdata.EntryNameElementSize] = 0; } _sounds = new SoundEffect[wavebankdata.EntryCount]; _streams = new StreamInfo[wavebankdata.EntryCount]; reader.BaseStream.Seek(wavebank_offset, SeekOrigin.Begin); // The compact format requires us to load stuff differently. var isCompactFormat = (wavebankdata.Flags & Flag_Compact) != 0; if (isCompactFormat) { // Load the sound data offset table from disk. for (var i = 0; i < wavebankdata.EntryCount; i++) { var len = reader.ReadInt32(); _streams[i].Format = wavebankdata.CompactFormat; _streams[i].FileOffset = (len & ((1 << 21) - 1)) * wavebankdata.Alignment; } // Now figure out the sound data lengths. for (var i = 0; i < wavebankdata.EntryCount; i++) { int nextOffset; if (i == (wavebankdata.EntryCount - 1)) { nextOffset = wavebankheader.Segments[last_segment].Length; } else { nextOffset = _streams[i + 1].FileOffset; } // The next and current offsets used to calculate the length. _streams[i].FileLength = nextOffset - _streams[i].FileOffset; } } else { for (var i = 0; i < wavebankdata.EntryCount; i++) { var info = new StreamInfo(); if (wavebankheader.Version == 1) { info.Format = reader.ReadInt32(); info.FileOffset = reader.ReadInt32(); info.FileLength = reader.ReadInt32(); info.LoopStart = reader.ReadInt32(); info.LoopLength = reader.ReadInt32(); } else { var flagsAndDuration = reader.ReadInt32(); // Unused if (wavebankdata.EntryMetaDataElementSize >= 8) { info.Format = reader.ReadInt32(); } if (wavebankdata.EntryMetaDataElementSize >= 12) { info.FileOffset = reader.ReadInt32(); } if (wavebankdata.EntryMetaDataElementSize >= 16) { info.FileLength = reader.ReadInt32(); } if (wavebankdata.EntryMetaDataElementSize >= 20) { info.LoopStart = reader.ReadInt32(); } if (wavebankdata.EntryMetaDataElementSize >= 24) { info.LoopLength = reader.ReadInt32(); } } // TODO: What is this doing? if (wavebankdata.EntryMetaDataElementSize < 24) { if (info.FileLength != 0) { info.FileLength = wavebankheader.Segments[last_segment].Length; } } _streams[i] = info; } } // If this isn't a streaming wavebank then load all the sounds now. if (!_streaming) { for (var i = 0; i < _streams.Length; i++) { var info = _streams[i]; // Read the data. reader.BaseStream.Seek(info.FileOffset + _playRegionOffset, SeekOrigin.Begin); // TODO: pool this memory var audiodata = reader.ReadBytes(info.FileLength); DecodeFormat(info.Format, out MiniFormatTag codec, out int channels, out int rate, out int alignment); // Call the special constuctor on SoundEffect to sort it out. _sounds[i] = new SoundEffect( audiodata, codec, channels, rate, alignment, info.LoopStart, info.LoopLength); } _streams = null; } } audioEngine.Wavebanks[_bankName] = this; IsPrepared = true; }
/// <param name="audioEngine">The engine that will be associated with this sound bank.</param> /// <param name="fileName">Path to a .xsb sound bank file.</param> public SoundBank(AudioEngine audioEngine, string fileName) { _audioengine = audioEngine ?? throw new ArgumentNullException(nameof(audioEngine)); if (string.IsNullOrEmpty(fileName)) { throw new ArgumentNullException(nameof(fileName)); } using (var stream = AudioEngine.OpenStream(fileName)) using (var reader = new BinaryReader(stream)) { // Thanks to Liandril for "xactxtract" for some of the offsets. uint magic = reader.ReadUInt32(); if (magic != 0x4B424453) //"SDBK" { throw new Exception("Bad soundbank format"); } reader.ReadUInt16(); // toolVersion uint formatVersion = reader.ReadUInt16(); if (formatVersion != 43) { Debug.WriteLine("Warning: SoundBank format {0} not supported.", formatVersion); } reader.ReadUInt16(); // crc, TODO: Verify crc (FCS16) reader.ReadUInt32(); // lastModifiedLow reader.ReadUInt32(); // lastModifiedHigh reader.ReadByte(); // platform ??? uint numSimpleCues = reader.ReadUInt16(); uint numComplexCues = reader.ReadUInt16(); reader.ReadUInt16(); //unkn reader.ReadUInt16(); // numTotalCues uint numWaveBanks = reader.ReadByte(); reader.ReadUInt16(); // numSounds uint cueNameTableLen = reader.ReadUInt16(); reader.ReadUInt16(); //unkn uint simpleCuesOffset = reader.ReadUInt32(); uint complexCuesOffset = reader.ReadUInt32(); //unkn uint cueNamesOffset = reader.ReadUInt32(); reader.ReadUInt32(); //unkn reader.ReadUInt32(); // variationTablesOffset reader.ReadUInt32(); //unkn uint waveBankNameTableOffset = reader.ReadUInt32(); reader.ReadUInt32(); // cueNameHashTableOffset reader.ReadUInt32(); // cueNameHashValsOffset reader.ReadUInt32(); // soundsOffset //parse wave bank name table stream.Seek(waveBankNameTableOffset, SeekOrigin.Begin); _waveBanks = new WaveBank[numWaveBanks]; _waveBankNames = new string[numWaveBanks]; for (int i = 0; i < numWaveBanks; i++) { _waveBankNames[i] = System.Text.Encoding.UTF8 .GetString(reader.ReadBytes(64)).Replace("\0", "", StringComparison.Ordinal); } //parse cue name table stream.Seek(cueNamesOffset, SeekOrigin.Begin); string[] cueNames = System.Text.Encoding.UTF8 .GetString(reader.ReadBytes((int)cueNameTableLen)).Split('\0'); // Simple cues if (numSimpleCues > 0) { stream.Seek(simpleCuesOffset, SeekOrigin.Begin); for (int i = 0; i < numSimpleCues; i++) { reader.ReadByte(); // flags uint soundOffset = reader.ReadUInt32(); var oldPosition = stream.Position; stream.Seek(soundOffset, SeekOrigin.Begin); var sound = new XactSound(audioEngine, this, reader); stream.Seek(oldPosition, SeekOrigin.Begin); _entries.Add( cueNames[i], new XactCueEntry(new XactSound[] { sound }, _defaultProbability)); } } // Complex cues if (numComplexCues > 0) { stream.Seek(complexCuesOffset, SeekOrigin.Begin); for (int i = 0; i < numComplexCues; i++) { byte flags = reader.ReadByte(); if (((flags >> 2) & 1) != 0) { uint soundOffset = reader.ReadUInt32(); reader.ReadUInt32(); //unkn var oldPosition = stream.Position; stream.Seek(soundOffset, SeekOrigin.Begin); var sound = new XactSound(audioEngine, this, reader); stream.Seek(oldPosition, SeekOrigin.Begin); _entries.Add( cueNames[numSimpleCues + i], new XactCueEntry(new XactSound[] { sound }, _defaultProbability)); } else { uint variationTableOffset = reader.ReadUInt32(); reader.ReadUInt32(); // transitionTableOffset //parse variation table long savepos = stream.Position; stream.Seek(variationTableOffset, SeekOrigin.Begin); uint numEntries = reader.ReadUInt16(); uint variationflags = reader.ReadUInt16(); reader.ReadByte(); reader.ReadUInt16(); reader.ReadByte(); var cueSounds = new XactSound[numEntries]; var probs = new float[numEntries]; uint tableType = (variationflags >> 3) & 0x7; for (int j = 0; j < numEntries; j++) { switch (tableType) { case 0: //Wave { int trackIndex = reader.ReadUInt16(); int waveBankIndex = reader.ReadByte(); reader.ReadByte(); // weightMin reader.ReadByte(); // weightMax cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); break; } case 1: { uint soundOffset = reader.ReadUInt32(); reader.ReadByte(); // weightMin reader.ReadByte(); // weightMax var oldPosition = stream.Position; stream.Seek(soundOffset, SeekOrigin.Begin); cueSounds[j] = new XactSound(audioEngine, this, reader); stream.Seek(oldPosition, SeekOrigin.Begin); break; } case 3: { uint soundOffset = reader.ReadUInt32(); reader.ReadSingle(); // weightMin reader.ReadSingle(); // weightMax reader.ReadUInt32(); // flags var oldPosition = stream.Position; stream.Seek(soundOffset, SeekOrigin.Begin); cueSounds[j] = new XactSound(audioEngine, this, reader); stream.Seek(oldPosition, SeekOrigin.Begin); break; } case 4: //CompactWave { int trackIndex = reader.ReadUInt16(); int waveBankIndex = reader.ReadByte(); cueSounds[j] = new XactSound(this, waveBankIndex, trackIndex); break; } default: throw new NotSupportedException(); } } stream.Seek(savepos, SeekOrigin.Begin); _entries.Add( cueNames[numSimpleCues + i], new XactCueEntry(cueSounds, probs)); } // Instance limiting reader.ReadByte(); //instanceLimit reader.ReadUInt16(); //fadeInSec, divide by 1000f reader.ReadUInt16(); //fadeOutSec, divide by 1000f reader.ReadByte(); //instanceFlags } } } }