diff --git a/OpenRA.Game/FileFormats/AudLoader.cs b/OpenRA.Game/FileFormats/AudLoader.cs index ecae60cfad..9fefbd9a27 100644 --- a/OpenRA.Game/FileFormats/AudLoader.cs +++ b/OpenRA.Game/FileFormats/AudLoader.cs @@ -45,6 +45,55 @@ namespace OpenRA.FileFormats } public class AudLoader : ISoundLoader + { + bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound) + { + try + { + sound = new AudFormat(stream); + return true; + } + catch + { + // Not a supported AUD + } + + sound = null; + return false; + } + } + + public class AudFormat : ISoundFormat + { + public int Channels { get { return 1; } } + public int SampleBits { get { return 16; } } + public int SampleRate { get { return sampleRate; } } + public float LengthInSeconds { get { return AudReader.SoundLength(stream); } } + public Stream GetPCMInputStream() { return new MemoryStream(rawData); } + + int sampleRate; + byte[] rawData; + + Stream stream; + + public AudFormat(Stream stream) + { + this.stream = stream; + + var position = stream.Position; + try + { + if (!AudReader.LoadSound(stream, out rawData, out sampleRate)) + throw new InvalidDataException(); + } + finally + { + stream.Position = position; + } + } + } + + public class AudReader { static readonly int[] IndexAdjust = { -1, -1, -1, -1, 2, 4, 6, 8 }; static readonly int[] StepTable = @@ -166,38 +215,5 @@ namespace OpenRA.FileFormats rawData = output; return true; } - - public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, - out int sampleRate) - { - channels = sampleBits = sampleRate = 0; - var position = stream.Position; - - try - { - if (!LoadSound(stream, out rawData, out sampleRate)) - return false; - } - catch (Exception e) - { - // LoadSound() will check if the stream is in a format that this parser supports. - // If not, it will simply return false so we know we can't use it. If it is, it will start - // parsing the data without any further failsafes, which means that it will crash on corrupted files - // (that end prematurely or otherwise don't conform to the specifications despite the headers being OK). - Log.Write("sound", "Failed to parse AUD file {0}. Error message:".F(fileName)); - Log.Write("sound", e.ToString()); - rawData = null; - return false; - } - finally - { - stream.Position = position; - } - - channels = 1; - sampleBits = 16; - - return true; - } } } diff --git a/OpenRA.Game/FileFormats/VocLoader.cs b/OpenRA.Game/FileFormats/VocLoader.cs index 157c2467ce..e291d68bc8 100644 --- a/OpenRA.Game/FileFormats/VocLoader.cs +++ b/OpenRA.Game/FileFormats/VocLoader.cs @@ -18,38 +18,30 @@ namespace OpenRA.FileFormats { public class VocLoader : ISoundLoader { - bool ISoundLoader.TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, out int sampleRate) + bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound) { - var position = stream.Position; - try { - var vocStream = new VocStream(stream); - channels = vocStream.Channels; - sampleBits = vocStream.BitsPerSample; - sampleRate = vocStream.SampleRate; - rawData = vocStream.ReadAllBytes(); + sound = new VocFormat(stream); + return true; } catch { - rawData = null; - channels = sampleBits = sampleRate = 0; - return false; - } - finally - { - stream.Position = position; + // Not a (supported) WAV } - return true; + sound = null; + return false; } } - public class VocStream : Stream + public class VocFormat : ISoundFormat { - public int BitsPerSample { get { return 8; } } + public int SampleBits { get { return 8; } } public int Channels { get { return 1; } } public int SampleRate { get; private set; } + public float LengthInSeconds { get { return (float)totalSamples / SampleRate; } } + public Stream GetPCMInputStream() { return new VocStream(this); } int totalSamples = 0; int samplePosition = 0; @@ -98,9 +90,10 @@ namespace OpenRA.FileFormats public int Count; } - public VocStream(Stream stream) + public VocFormat(Stream stream) { this.stream = stream; + CheckVocHeader(); Preload(); } @@ -187,7 +180,7 @@ namespace OpenRA.FileFormats break; } - // Silence + // Silence case 3: { if (block.Length != 3) @@ -199,7 +192,7 @@ namespace OpenRA.FileFormats break; } - // Repeat start + // Repeat start case 6: { if (block.Length != 2) @@ -208,11 +201,11 @@ namespace OpenRA.FileFormats break; } - // Repeat end + // Repeat end case 7: break; - // Extra info + // Extra info case 8: { if (block.Length != 4) @@ -316,44 +309,7 @@ namespace OpenRA.FileFormats } } - public byte[] ReadAllBytes() - { - Rewind(); - var buffer = new byte[totalSamples]; - Read(buffer, 0, totalSamples); - return buffer; - } - - public override bool CanRead { get { return true; } } - - public override bool CanSeek { get { return false; } } - - public override bool CanWrite { get { return false; } } - - public override long Length { get { return totalSamples; } } - - public override long Position - { - get { return samplePosition; } - set { throw new NotImplementedException(); } - } - - public override void Flush() - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) + int Read(byte[] buffer, int offset, int count) { var bytesWritten = 0; var samplesLeft = Math.Min(count, buffer.Length - offset); @@ -362,7 +318,7 @@ namespace OpenRA.FileFormats var len = FillBuffer(samplesLeft); if (len == 0) break; - Array.Copy(this.buffer, 0, buffer, offset, len); + Buffer.BlockCopy(this.buffer, 0, buffer, offset, len); samplesLeft -= len; offset += len; bytesWritten += len; @@ -371,9 +327,34 @@ namespace OpenRA.FileFormats return bytesWritten; } - public override void Write(byte[] buffer, int offset, int count) + public class VocStream : Stream { - throw new NotImplementedException(); + VocFormat format; + public VocStream(VocFormat format) + { + this.format = format; + } + + public override bool CanRead { get { return format.samplePosition < format.totalSamples; } } + public override bool CanSeek { get { return false; } } + public override bool CanWrite { get { return false; } } + + public override long Length { get { return format.totalSamples; } } + public override long Position + { + get { return format.samplePosition; } + set { throw new NotImplementedException(); } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return format.Read(buffer, offset, count); + } + + public override void Flush() { throw new NotImplementedException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } + public override void SetLength(long value) { throw new NotImplementedException(); } + public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } } } } diff --git a/OpenRA.Game/FileFormats/WavLoader.cs b/OpenRA.Game/FileFormats/WavLoader.cs index e449ffa7c5..c19a2ec373 100644 --- a/OpenRA.Game/FileFormats/WavLoader.cs +++ b/OpenRA.Game/FileFormats/WavLoader.cs @@ -15,6 +15,63 @@ using System.IO; namespace OpenRA.FileFormats { public class WavLoader : ISoundLoader + { + bool ISoundLoader.TryParseSound(Stream stream, out ISoundFormat sound) + { + try + { + sound = new WavFormat(stream); + return true; + } + catch + { + // Not a (supported) WAV + } + + sound = null; + return false; + } + } + + public class WavFormat : ISoundFormat + { + public int Channels { get { return channels; } } + public int SampleBits { get { return sampleBits; } } + public int SampleRate { get { return sampleRate; } } + public float LengthInSeconds { get { return WavReader.WaveLength(stream); } } + public Stream GetPCMInputStream() { return new MemoryStream(rawData); } + + int channels; + int sampleBits; + int sampleRate; + byte[] rawData; + + readonly Stream stream; + + public WavFormat(Stream stream) + { + this.stream = stream; + var wavReader = new WavReader(); + + var position = stream.Position; + try + { + if (!wavReader.LoadSound(stream)) + throw new InvalidDataException(); + } + finally + { + stream.Position = position; + } + + rawData = wavReader.RawOutput; + channels = wavReader.Channels; + sampleBits = wavReader.BitsPerSample; + sampleRate = wavReader.SampleRate; + } + } + + public class WavReader { public int FileSize; public string Format; @@ -34,7 +91,7 @@ namespace OpenRA.FileFormats public enum WaveType { Pcm = 0x1, ImaAdpcm = 0x11 } public static WaveType Type { get; private set; } - bool LoadSound(Stream s) + public bool LoadSound(Stream s) { var type = s.ReadASCII(4); if (type != "RIFF") @@ -176,40 +233,5 @@ namespace OpenRA.FileFormats return output; } - - public bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, - out int sampleBits, out int sampleRate) - { - rawData = null; - channels = sampleBits = sampleRate = 0; - var position = stream.Position; - - try - { - if (!LoadSound(stream)) - return false; - } - catch (Exception e) - { - // LoadSound() will check if the stream is in a format that this parser supports. - // If not, it will simply return false so we know we can't use it. If it is, it will start - // parsing the data without any further failsafes, which means that it will crash on corrupted files - // (that end prematurely or otherwise don't conform to the specifications despite the headers being OK). - Log.Write("sound", "Failed to parse WAV file {0}. Error message:".F(fileName)); - Log.Write("sound", e.ToString()); - return false; - } - finally - { - stream.Position = position; - } - - rawData = RawOutput; - channels = Channels; - sampleBits = BitsPerSample; - sampleRate = SampleRate; - - return true; - } } } \ No newline at end of file diff --git a/OpenRA.Game/FileSystem/BagFile.cs b/OpenRA.Game/FileSystem/BagFile.cs index 6f5b8bd2f8..351fa4ccd4 100644 --- a/OpenRA.Game/FileSystem/BagFile.cs +++ b/OpenRA.Game/FileSystem/BagFile.cs @@ -68,7 +68,7 @@ namespace OpenRA.FileSystem waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE")); waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt ")); waveHeaderMemoryStream.Write(BitConverter.GetBytes(16)); - waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.Pcm)); + waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.Pcm)); waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels)); waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate)); waveHeaderMemoryStream.Write(BitConverter.GetBytes(2 * channels * entry.SampleRate)); @@ -90,7 +90,7 @@ namespace OpenRA.FileSystem waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("WAVE")); waveHeaderMemoryStream.Write(Encoding.ASCII.GetBytes("fmt ")); waveHeaderMemoryStream.Write(BitConverter.GetBytes(20)); - waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavLoader.WaveType.ImaAdpcm)); + waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)WavReader.WaveType.ImaAdpcm)); waveHeaderMemoryStream.Write(BitConverter.GetBytes((short)channels)); waveHeaderMemoryStream.Write(BitConverter.GetBytes(entry.SampleRate)); waveHeaderMemoryStream.Write(BitConverter.GetBytes(bytesPerSec)); diff --git a/OpenRA.Game/GameRules/MusicInfo.cs b/OpenRA.Game/GameRules/MusicInfo.cs index c4dd887410..f0d424b97c 100644 --- a/OpenRA.Game/GameRules/MusicInfo.cs +++ b/OpenRA.Game/GameRules/MusicInfo.cs @@ -44,9 +44,9 @@ namespace OpenRA.GameRules using (var s = filesystem.Open(Filename)) { if (Filename.ToLowerInvariant().EndsWith("wav")) - Length = (int)WavLoader.WaveLength(s); + Length = (int)WavReader.WaveLength(s); else - Length = (int)AudLoader.SoundLength(s); + Length = (int)AudReader.SoundLength(s); } } } diff --git a/OpenRA.Game/Sound/Sound.cs b/OpenRA.Game/Sound/Sound.cs index 6cdf64a2d0..3b996ee329 100644 --- a/OpenRA.Game/Sound/Sound.cs +++ b/OpenRA.Game/Sound/Sound.cs @@ -21,7 +21,16 @@ namespace OpenRA { public interface ISoundLoader { - bool TryParseSound(Stream stream, string fileName, out byte[] rawData, out int channels, out int sampleBits, out int sampleRate); + bool TryParseSound(Stream stream, out ISoundFormat sound); + } + + public interface ISoundFormat + { + int Channels { get; } + int SampleBits { get; } + int SampleRate { get; } + float LengthInSeconds { get; } + Stream GetPCMInputStream(); } public sealed class Sound : IDisposable @@ -35,7 +44,7 @@ namespace OpenRA public Sound(string engineName) { - var enginePath = Platform.ResolvePath(Path.Combine(".", "OpenRA.Platforms." + engineName + ".dll")); + var enginePath = Platform.ResolvePath(".", "OpenRA.Platforms." + engineName + ".dll"); soundEngine = CreateDevice(Assembly.LoadFile(enginePath)); } @@ -60,16 +69,17 @@ namespace OpenRA using (var stream = fileSystem.Open(filename)) { - byte[] rawData; - int channels; - int sampleBits; - int sampleRate; - foreach (var loader in loaders) - if (loader.TryParseSound(stream, filename, out rawData, out channels, out sampleBits, out sampleRate)) - return soundEngine.AddSoundSourceFromMemory(rawData, channels, sampleBits, sampleRate); - - throw new InvalidDataException(filename + " is not a valid sound file!"); + ISoundFormat soundFormat; + foreach (var loader in Game.ModData.SoundLoaders) + { + stream.Position = 0; + if (loader.TryParseSound(stream, out soundFormat)) + return soundEngine.AddSoundSourceFromMemory( + soundFormat.GetPCMInputStream().ReadAllBytes(), soundFormat.Channels, soundFormat.SampleBits, soundFormat.SampleRate); + } } + + throw new InvalidDataException(filename + " is not a valid sound file!"); } public void Initialize(ISoundLoader[] loaders, IReadOnlyFileSystem fileSystem) diff --git a/OpenRA.Mods.Common/FileFormats/VqaReader.cs b/OpenRA.Mods.Common/FileFormats/VqaReader.cs index 8e024a0462..eec719f02e 100644 --- a/OpenRA.Mods.Common/FileFormats/VqaReader.cs +++ b/OpenRA.Mods.Common/FileFormats/VqaReader.cs @@ -221,7 +221,7 @@ namespace OpenRA.Mods.Common.FileFormats } if (audioChannels == 1) - audioData = compressed ? AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray(); + audioData = compressed ? AudReader.LoadSound(audio1.ToArray(), ref adpcmIndex) : audio1.ToArray(); else { byte[] leftData, rightData; @@ -233,9 +233,9 @@ namespace OpenRA.Mods.Common.FileFormats else { adpcmIndex = 0; - leftData = AudLoader.LoadSound(audio1.ToArray(), ref adpcmIndex); + leftData = AudReader.LoadSound(audio1.ToArray(), ref adpcmIndex); adpcmIndex = 0; - rightData = AudLoader.LoadSound(audio2.ToArray(), ref adpcmIndex); + rightData = AudReader.LoadSound(audio2.ToArray(), ref adpcmIndex); } audioData = new byte[rightData.Length + leftData.Length];