diff --git a/GameRes/Audio.cs b/GameRes/Audio.cs new file mode 100644 index 00000000..be0cc68c --- /dev/null +++ b/GameRes/Audio.cs @@ -0,0 +1,167 @@ +//! \file Audio.cs +//! \date Tue Nov 04 17:35:54 2014 +//! \brief audio format class. +// +// Copyright (C) 2014 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.IO; + +namespace GameRes +{ + public struct WaveFormat + { + public ushort FormatTag; + public ushort Channels; + public uint SamplesPerSecond; + public uint AverageBytesPerSecond; + public ushort BlockAlign; + public ushort BitsPerSample; + } + + public abstract class SoundInput : Stream + { + protected Stream m_input; + protected long m_pcm_size; + + public override bool CanRead { get { return m_input.CanRead; } } + public override bool CanWrite { get { return false; } } + public override long Length { get { return m_pcm_size; } } + + public long PcmSize + { + get { return m_pcm_size; } + protected set { m_pcm_size = value; } + } + + public abstract int SourceBitrate { get; } + + public WaveFormat Format { get; protected set; } + + protected SoundInput (Stream input) + { + m_input = input; + } + + public virtual void Reset () + { + Position = 0; + } + + #region System.IO.Stream methods + public override void Flush() + { + m_input.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + if (origin == SeekOrigin.Begin) + Position = offset; + else if (origin == SeekOrigin.Current) + Position += offset; + else + Position = Length + offset; + return Position; + } + + public override void SetLength (long length) + { + throw new System.NotSupportedException ("SoundInput.SetLength method is not supported"); + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new System.NotSupportedException ("SoundInput.Write method is not supported"); + } + + public override void WriteByte (byte value) + { + throw new System.NotSupportedException ("SoundInput.WriteByte method is not supported"); + } + #endregion + + #region IDisposable Members + bool disposed = false; + protected override void Dispose (bool disposing) + { + if (!disposed) + { + if (disposing) + { + m_input.Dispose(); + } + disposed = true; + base.Dispose (disposing); + } + } + #endregion + } + + public abstract class AudioFormat : IResource + { + public override string Type { get { return "audio"; } } + + public abstract SoundInput TryOpen (Stream file); + + public static SoundInput Read (Stream file) + { + var input = new MemoryStream(); + file.CopyTo (input); + try + { + var sound = FindFormat (input); + if (null != sound) + input = null; // input stream is owned by sound object now, don't dispose it + return sound; + } + finally + { + if (null != input) + input.Dispose(); + } + } + + public static SoundInput FindFormat (Stream file) + { + uint signature = FormatCatalog.ReadSignature (file); + for (;;) + { + var range = FormatCatalog.Instance.LookupSignature (signature); + foreach (var impl in range) + { + try + { + file.Position = 0; + SoundInput sound = impl.TryOpen (file); + if (null != sound) + return sound; + } + catch { } + } + if (0 == signature) + break; + signature = 0; + } + return null; + } + } +} diff --git a/GameRes/AudioWAV.cs b/GameRes/AudioWAV.cs new file mode 100644 index 00000000..a6335459 --- /dev/null +++ b/GameRes/AudioWAV.cs @@ -0,0 +1,158 @@ +//! \file AudioWAV.cs +//! \date Tue Nov 04 18:22:37 2014 +//! \brief WAVE audio format implementation. +// +// Copyright (C) 2014 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// + +using System.ComponentModel.Composition; +using System.IO; + +namespace GameRes +{ + public class WaveInput : SoundInput + { + long m_data_offset; + + public override long Position + { + get { return m_input.Position - m_data_offset; } + set { m_input.Position = m_data_offset + value; } + } + + public override bool CanSeek { get { return m_input.CanSeek; } } + + public override int SourceBitrate + { + get { return (int)Format.AverageBytesPerSecond * 8; } + } + + public WaveInput (Stream file) : base (file) + { + using (var input = new BinaryReader (m_input, System.Text.Encoding.UTF8, true)) + { + input.BaseStream.Seek (8, SeekOrigin.Current); + uint header = input.ReadUInt32(); + if (header != 0x45564157) + throw new InvalidFormatException ("Invalid WAVE file format."); + + bool found_fmt = false; + bool found_data = false; + long current_offset = input.BaseStream.Position; + + while (!found_fmt || !found_data) + { + uint header0 = input.ReadUInt32(); + uint header1 = input.ReadUInt32(); + + if (!found_fmt && 0x20746d66 == header0) + { + if (header1 < 0x10) + throw new InvalidFormatException ("Invalid WAVE file format"); + + ushort tag = input.ReadUInt16(); + if (1 != tag) + throw new InvalidFormatException ("Unsupported WAVE file format."); + + var format = new WaveFormat(); + format.FormatTag = tag; + format.Channels = input.ReadUInt16(); + format.SamplesPerSecond = input.ReadUInt32(); + format.AverageBytesPerSecond = input.ReadUInt32(); + format.BlockAlign = input.ReadUInt16(); + format.BitsPerSample = input.ReadUInt16(); + this.Format = format; + + found_fmt = true; + current_offset += 8 + ((header1 + 1) & ~1); + input.BaseStream.Seek (current_offset, SeekOrigin.Begin); + continue; + } + if (!found_data && 0x61746164 == header0) + { + found_data = true; + m_data_offset = current_offset + 8; + this.PcmSize = header1; + if (found_fmt) + break; + } + long chunk_size = (header1 + 1) & ~1; + input.BaseStream.Seek (chunk_size, SeekOrigin.Current); + + current_offset += 8 + chunk_size; + } + this.Reset(); + } + } + + public override void Reset () + { + m_input.Seek (m_data_offset, SeekOrigin.Begin); + } + + public override long Seek (long offset, SeekOrigin origin) + { + if (SeekOrigin.Begin == origin) + offset += m_data_offset; + else if (SeekOrigin.Current == origin) + offset = m_input.Position + offset; + else if (SeekOrigin.End == origin) + offset = m_data_offset + PcmSize + offset; + + if (offset < m_data_offset) + offset = m_data_offset; + else if (offset > m_data_offset + PcmSize) + offset = m_data_offset + PcmSize; + + offset = m_input.Seek (offset, SeekOrigin.Begin); + return offset - m_data_offset; + } + + public override int Read (byte[] buffer, int offset, int count) + { + long remaining = PcmSize - Position; + if (count > remaining) + count = (int)remaining; + return m_input.Read (buffer, offset, count); + } + + public override int ReadByte () + { + if (Position < PcmSize) + return m_input.ReadByte(); + else + return -1; + } + } + + [Export(typeof(AudioFormat))] + public class WaveAudio : AudioFormat + { + public override string Tag { get { return "WAV"; } } + public override string Description { get { return "Wave audio format"; } } + public override uint Signature { get { return 0x46464952; } } // 'RIFF' + + public override SoundInput TryOpen (Stream file) + { + return new WaveInput (file); + } + } +} diff --git a/GameRes/GameRes.cs b/GameRes/GameRes.cs index d2226ed5..eb86afe5 100644 --- a/GameRes/GameRes.cs +++ b/GameRes/GameRes.cs @@ -276,6 +276,8 @@ namespace GameRes private IEnumerable m_arc_formats; [ImportMany(typeof(ImageFormat))] private IEnumerable m_image_formats; + [ImportMany(typeof(AudioFormat))] + private IEnumerable m_audio_formats; [ImportMany(typeof(ScriptFormat))] private IEnumerable m_script_formats; #pragma warning restore 649 @@ -288,6 +290,7 @@ namespace GameRes public IEnumerable ArcFormats { get { return m_arc_formats; } } public IEnumerable ImageFormats { get { return m_image_formats; } } + public IEnumerable AudioFormats { get { return m_audio_formats; } } public IEnumerable ScriptFormats { get { return m_script_formats; } } public Exception LastError { get; set; } @@ -310,6 +313,7 @@ namespace GameRes container.ComposeParts (this); AddResourceImpl (m_arc_formats); AddResourceImpl (m_image_formats); + AddResourceImpl (m_audio_formats); AddResourceImpl (m_script_formats); } diff --git a/GameRes/GameRes.csproj b/GameRes/GameRes.csproj index 310d959a..ac1f5660 100644 --- a/GameRes/GameRes.csproj +++ b/GameRes/GameRes.csproj @@ -51,6 +51,8 @@ + +