public static bool SaveToXml(string filename, WadSounds sounds) { using (var fileStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) { var serializer = new XmlSerializer(typeof(WadSounds)); serializer.Serialize(fileStream, sounds); } return(true); }
private static WadSounds ReadFromXml(string filename) { var sounds = new WadSounds(); using (var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None)) { var serializer = new XmlSerializer(typeof(WadSounds)); sounds = (WadSounds)serializer.Deserialize(fileStream); } foreach (var soundInfo in sounds.SoundInfos) { soundInfo.SoundCatalog = filename; } return(sounds); }
public int SampleCount(LevelSettings settings) { if (settings.GameVersion.UsesMainSfx() || Samples == null || Samples.Count <= 0) { return(-1); } int result = 0; foreach (var sample in Samples) { var path = WadSounds.TryGetSamplePath(settings, sample.FileName); if (path != null) { result++; } } return(result); }
public static Wad2 LoadFromFile(string fileName, bool withSounds) { Wad2 result; using (var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) result = LoadFromStream(fileStream); result.FileName = fileName; // Load additional XML file if it exists if (withSounds) { var xmlFile = Path.ChangeExtension(fileName, "xml"); if (File.Exists(xmlFile)) { result.Sounds = WadSounds.ReadFromFile(xmlFile); } } return(result); }
public static void SaveToFile(Wad2 wad, string filename) { // We save first to a temporary memory stream using (var stream = new MemoryStream()) { SaveToStream(wad, stream); // Save to temporary file as well, so original wad2 won't vanish in case of crash var tempName = filename + ".tmp"; if (File.Exists(tempName)) { File.Delete(tempName); } stream.Seek(0, SeekOrigin.Begin); using (var writer = new BinaryWriter(new FileStream(tempName, FileMode.Create, FileAccess.Write, FileShare.None))) { var buffer = stream.ToArray(); writer.Write(buffer, 0, buffer.Length); } // Save successful, write temp file over original (if exists) if (File.Exists(filename)) { File.Delete(filename); } File.Move(tempName, filename); } // Save sounds to XML file if (wad.Sounds.SoundInfos.Count > 0) { string xmlFilename = Path.Combine(Path.GetDirectoryName(filename), Path.GetFileNameWithoutExtension(filename) + ".xml"); WadSounds.SaveToXml(xmlFilename, wad.Sounds); } }
public static SortedDictionary <int, WadSample> CompileSamples(List <WadSoundInfo> soundMap, LevelSettings settings, bool onlyIndexed, IProgressReporter reporter, out bool samplesMissing) { var samples = new List <WadSample>(); foreach (var soundInfo in soundMap) { if (onlyIndexed && !soundInfo.Indexed) { continue; } foreach (var sample in soundInfo.Samples) { samples.Add(sample); } } var loadedSamples = new SortedDictionary <int, WadSample>(); // Set up maximum buffer sizes and sample rate int maxBufferLength = 1024 * 256; int warnBufferLength = 1024 * 256; uint supportedSampleRate = 22050; uint supportedBitness = 16; switch (settings.GameVersion) { case TRVersion.Game.TR5Main: maxBufferLength = int.MaxValue; // Unlimited warnBufferLength = int.MaxValue; break; case TRVersion.Game.TR4: case TRVersion.Game.TRNG: maxBufferLength = 1024 * 1024; // Raised TREP limit break; case TRVersion.Game.TR1: case TRVersion.Game.TR2: supportedSampleRate = 11025; supportedBitness = 8; break; } var missing = false; Parallel.For(0, samples.Count, i => { WadSample currentSample = NullSample; try { string samplePath = WadSounds.TryGetSamplePath(settings, samples[i].FileName); // If sample was found, then load it... if (!string.IsNullOrEmpty(samplePath)) { using (var stream = new FileStream(samplePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { var buffer = new byte[stream.Length]; if (stream.Read(buffer, 0, buffer.Length) != buffer.Length) { throw new EndOfStreamException(); } if (settings.GameVersion.UsesMainSfx()) { var sampleRate = settings.GameVersion < TRVersion.Game.TR3 ? 11025 : 22050; currentSample = new WadSample(samplePath, ConvertSampleFormat(buffer, true, (uint)sampleRate)); } else { currentSample = new WadSample(samplePath, ConvertSampleFormat(buffer, false)); } if (currentSample.SampleRate != supportedSampleRate) { reporter?.ReportWarn("Sample " + samplePath + " has a sample rate of " + currentSample.SampleRate + " which is unsupported for this engine version."); } if (currentSample.ChannelCount > 1) { reporter?.ReportWarn("Sample " + samplePath + " isn't mono. Only mono samples are supported. Crashes may occur."); } if (currentSample.BitsPerSample != supportedBitness) { reporter?.ReportWarn("Sample " + samplePath + " is not " + supportedBitness + "-bit sample and is not supported in this game version. Crashes may occur."); } if (buffer.Length > maxBufferLength) { reporter?.ReportWarn("Sample " + samplePath + " is more than " + maxBufferLength / 1024 + " kbytes long. It is too big for this game version, crashes may occur."); } else if (buffer.Length > warnBufferLength) { reporter?.ReportWarn("Sample " + samplePath + " is more than " + warnBufferLength / 1024 + " kbytes long. It may cause problems without additional measures, such as patching."); } } } // ... otherwise output null sample else { currentSample = WadSample.NullSample; logger.Warn(new FileNotFoundException(), "Unable to find sample '" + samplePath + "'"); missing = true; } } catch (Exception exc) { logger.Warn(exc, "Unable to read file '" + samples[i].FileName + "' from provided location."); } lock (loadedSamples) loadedSamples.Add(i, currentSample); }); if (missing) { reporter?.ReportWarn("Some samples are missing. Make sure sample paths are specified correctly. Check level settings for details."); } samplesMissing = missing; return(loadedSamples); }
public Wad2() { Sounds = new WadSounds(); }
private static WadSounds ReadFromTxt(string filename) { var sounds = new WadSounds(); try { using (var fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None)) { using (var reader = new StreamReader(fileStream)) { ushort soundId = 0; while (!reader.EndOfStream) { var s = reader.ReadLine().Trim(); var sound = new WadSoundInfo(soundId); // Get the name (ends with :) int endOfSoundName = s.IndexOf(':'); if (endOfSoundName == -1) { soundId++; continue; } sound.Name = s.Substring(0, endOfSoundName); sound.SoundCatalog = filename; // Get everything else and remove empty tokens var tokens = s.Substring(endOfSoundName + 1).Split(' ', '\t').Where(token => !string.IsNullOrEmpty(token)).ToList(); if (tokens.Count <= 1) { soundId++; continue; } // Trim comments int commentTokenIndex = tokens.FindIndex(t => t.StartsWith(";")); if (commentTokenIndex != -1) { tokens.RemoveRange(commentTokenIndex, tokens.Count - commentTokenIndex); } var anyTokenFound = false; for (int i = 0; i < tokens.Count; i++) { var token = tokens[i]; short temp = 0; if (token.StartsWith("PIT", StringComparison.InvariantCultureIgnoreCase) && short.TryParse(token.Substring(3), out temp)) { sound.PitchFactor = temp; anyTokenFound = true; } else if (token.StartsWith("RAD", StringComparison.InvariantCultureIgnoreCase) && short.TryParse(token.Substring(3), out temp)) { sound.RangeInSectors = temp; anyTokenFound = true; } else if (token.StartsWith("VOL", StringComparison.InvariantCultureIgnoreCase) && short.TryParse(token.Substring(3), out temp)) { sound.Volume = temp; anyTokenFound = true; } else if (token.StartsWith("CH", StringComparison.InvariantCultureIgnoreCase) && short.TryParse(token.Substring(2), out temp)) { sound.Chance = temp; anyTokenFound = true; } else if (token.Equals("P", StringComparison.InvariantCultureIgnoreCase)) { sound.RandomizePitch = true; anyTokenFound = true; } else if (token.Equals("V", StringComparison.InvariantCultureIgnoreCase)) { sound.RandomizeVolume = true; anyTokenFound = true; } else if (token.Equals("N", StringComparison.InvariantCultureIgnoreCase)) { sound.DisablePanning = true; anyTokenFound = true; } else if (token.Equals("L", StringComparison.InvariantCultureIgnoreCase)) { sound.LoopBehaviour = WadSoundLoopBehaviour.Looped; anyTokenFound = true; } else if (token.Equals("R", StringComparison.InvariantCultureIgnoreCase)) { sound.LoopBehaviour = WadSoundLoopBehaviour.OneShotRewound; anyTokenFound = true; } else if (token.Equals("W", StringComparison.InvariantCultureIgnoreCase)) { sound.LoopBehaviour = WadSoundLoopBehaviour.OneShotWait; anyTokenFound = true; } else if (token.StartsWith("#g")) { sound.Global = true; anyTokenFound = true; } else if (!token.StartsWith("#") && !anyTokenFound) { sound.Samples.Add(new WadSample(token + ".wav")); } if (token.StartsWith("#")) { sound.Indexed = true; } } sounds.SoundInfos.Add(sound); soundId++; } } } } catch (Exception ex) { return(null); } return(sounds); }
private static WadSounds ReadFromSfx(string filename) { var samples = new List <string>(); // Read version int version = 129; using (var readerVersion = new BinaryReaderEx(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))) version = readerVersion.ReadInt32(); // Read samples var samPath = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".sam"; using (var readerSounds = new StreamReader(new FileStream(samPath, FileMode.Open, FileAccess.Read, FileShare.Read))) while (!readerSounds.EndOfStream) { samples.Add(readerSounds.ReadLine()); } // Read sounds int soundMapSize = 0; short[] soundMap; var wadSoundInfos = new List <wad_sound_info>(); var soundInfos = new List <WadSoundInfo>(); var sfxPath = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".sfx"; using (var readerSfx = new BinaryReaderEx(new FileStream(sfxPath, FileMode.Open, FileAccess.Read, FileShare.Read))) { // Try to guess the WAD version readerSfx.BaseStream.Seek(740, SeekOrigin.Begin); short first = readerSfx.ReadInt16(); short second = readerSfx.ReadInt16(); version = ((first == -1 || second == (first + 1)) ? 130 : 129); readerSfx.BaseStream.Seek(0, SeekOrigin.Begin); soundMapSize = (version == 130 ? 2048 : 370); soundMap = new short[soundMapSize]; for (var i = 0; i < soundMapSize; i++) { soundMap[i] = readerSfx.ReadInt16(); } var numSounds = readerSfx.ReadUInt32(); for (var i = 0; i < numSounds; i++) { var info = new wad_sound_info(); info.Sample = readerSfx.ReadUInt16(); info.Volume = readerSfx.ReadByte(); info.Range = readerSfx.ReadByte(); info.Chance = readerSfx.ReadByte(); info.Pitch = readerSfx.ReadByte(); info.Characteristics = readerSfx.ReadUInt16(); wadSoundInfos.Add(info); } } // Convert old data to new format for (int i = 0; i < soundMapSize; i++) { // Check if sound is defined at all if (soundMap[i] == -1 || soundMap[i] >= wadSoundInfos.Count) { continue; } // Fill the new sound info var oldInfo = wadSoundInfos[soundMap[i]]; var newInfo = new WadSoundInfo(i); newInfo.Name = TrCatalog.GetOriginalSoundName(TRVersion.Game.TR4, (uint)i); newInfo.Volume = (int)Math.Round(oldInfo.Volume * 100.0f / 255.0f); newInfo.RangeInSectors = oldInfo.Range; newInfo.Chance = (int)Math.Round(oldInfo.Chance * 100.0f / 255.0f); if (newInfo.Chance == 0) { newInfo.Chance = 100; // Convert legacy chance value } newInfo.PitchFactor = (int)Math.Round((oldInfo.Pitch > 127 ? oldInfo.Pitch - 256 : oldInfo.Pitch) * 100.0f / 128.0f); newInfo.RandomizePitch = ((oldInfo.Characteristics & 0x2000) != 0); newInfo.RandomizeVolume = ((oldInfo.Characteristics & 0x4000) != 0); newInfo.DisablePanning = ((oldInfo.Characteristics & 0x1000) != 0); newInfo.LoopBehaviour = (WadSoundLoopBehaviour)(oldInfo.Characteristics & 0x03); newInfo.SoundCatalog = sfxPath; // Read all samples linked to this sound info (for example footstep has 4 samples) int numSamplesInGroup = (oldInfo.Characteristics & 0x00fc) >> 2; for (int j = oldInfo.Sample; j < oldInfo.Sample + numSamplesInGroup; j++) { newInfo.Samples.Add(new WadSample(samples[j])); } soundInfos.Add(newInfo); } var sounds = new WadSounds(soundInfos); return(sounds); }
public static void PlaySample(Level level, WadSample sample, int channel, float volume = 1.0f, float pitch = 1.0f, float pan = 0.0f, int loopCount = 1) { if (volume <= 0.0f || loopCount <= 0) { return; } var disposables = new List <IDisposable>(); try { // Load data. // If waveform data is loaded into memory, use it to play sound, otherwise find wav file on disk. WaveFileReader waveStream; MemoryStream memoryStream; if (sample.IsLoaded) { memoryStream = disposables.AddAndReturn(new MemoryStream(sample.Data, false)); waveStream = disposables.AddAndReturn(new WaveFileReader(memoryStream)); } else { var path = WadSounds.TryGetSamplePath(level.Settings, sample.FileName); if (path == null) { return; } else { waveStream = disposables.AddAndReturn(new WaveFileReader(path)); } } // Apply looping ISampleProvider sampleStream; if (loopCount <= 1) { sampleStream = waveStream.ToSampleProvider(); } else { sampleStream = new RepeatedStream(waveStream) { LoopCount = loopCount } }; // Always play sample as 22 khz if (sampleStream.WaveFormat.SampleRate != 22050) { sampleStream = new PitchedStream { Source = sampleStream, Pitch = 22050.0f / sampleStream.WaveFormat.SampleRate } } ; // Apply panning if (pan != 1.0f) { sampleStream = new PanningSampleProvider(sampleStream) { Pan = pan } } ; // Apply pitch if (pitch != 1.0f) { sampleStream = new PitchedStream { Source = sampleStream, Pitch = pitch } } ; // Apply volume if (volume != 1.0f) { sampleStream = new VolumeSampleProvider(sampleStream) { Volume = volume } } ; // Add some silence to make sure the audio plays out. const int latencyInMilliseconds = 200; int latencyInSamples = (sampleStream.WaveFormat.SampleRate * latencyInMilliseconds * 2) / 1000; sampleStream = new OffsetSampleProvider(sampleStream) { LeadOutSamples = sampleStream.WaveFormat.Channels * latencyInSamples }; // Play _channels[channel] = disposables.AddAndReturn(new WaveOut()); _channels[channel].Init(sampleStream); _channels[channel].PlaybackStopped += (s, e) => { foreach (IDisposable disposable in disposables) { disposable?.Dispose(); } _channels[channel] = null; _indices[channel] = -1; }; _channels[channel].Play(); } catch (Exception ex) { // Clean up in case of a problem foreach (IDisposable disposable in disposables) { disposable?.Dispose(); } _logger.Error("Error while playing sample " + sample + ", exception: " + ex); } }