From 0d6e3f9a97b6e8eeff8806d4bf7ca645bea4d72f Mon Sep 17 00:00:00 2001 From: morkt Date: Mon, 30 May 2016 15:41:15 +0400 Subject: [PATCH] implemented Creative Voice File format. --- ArcFormats/AudioVOC.cs | 184 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 ArcFormats/AudioVOC.cs diff --git a/ArcFormats/AudioVOC.cs b/ArcFormats/AudioVOC.cs new file mode 100644 index 00000000..e9d50d77 --- /dev/null +++ b/ArcFormats/AudioVOC.cs @@ -0,0 +1,184 @@ +//! \file AudioVOC.cs +//! \date Sun May 29 03:01:14 2016 +//! \brief Creative Voice audio format. +// +// Copyright (C) 2016 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; +using System.ComponentModel.Composition; +using System.IO; +using GameRes.Utility; + +namespace GameRes.Formats.Creative +{ + internal sealed class VocReader : IDisposable + { + int m_version; + int m_header_size; + BinaryReader m_input; + WaveFormat m_format; + + public WaveFormat Format { get { return m_format; } } + + public VocReader (Stream input, byte[] header) + { + m_header_size = LittleEndian.ToUInt16 (header, 0x14); + if (m_header_size < 0x1A) + throw new InvalidFormatException(); + m_version = LittleEndian.ToUInt16 (header, 0x16); + m_input = new ArcView.Reader (input); + } + + public Stream ConvertToPcm () + { + m_input.BaseStream.Position = m_header_size; + bool format_read = false; + var pcm = new MemoryStream(); + try + { + for (;;) + { + int block_type = m_input.BaseStream.ReadByte(); + if (-1 == block_type || 0 == block_type) + break; + int block_size = m_input.ReadUInt16(); + block_size |= m_input.ReadByte() << 16; + uint freq; + int codec = -1; + switch (block_type) + { + case 1: + freq = m_input.ReadByte(); + codec = m_input.ReadByte(); + Copy (block_size-2, pcm); + m_format.Channels = 1; + m_format.SamplesPerSecond = 1000000u / (256u - freq); + m_format.BitsPerSample = 8; + format_read = true; + break; + case 2: + Copy (block_size, pcm); + break; + case 8: + freq = m_input.ReadUInt16(); + codec = m_input.ReadByte(); + m_format.Channels = (ushort)(m_input.ReadByte()+1); + m_format.SamplesPerSecond = 256000000u / (Format.Channels * (65536u - freq)); + m_format.BitsPerSample = 8; + format_read = true; + break; + case 9: + m_format.SamplesPerSecond = m_input.ReadUInt32(); + m_format.BitsPerSample = m_input.ReadByte(); + m_format.Channels = (ushort)(m_input.ReadByte()+1); + codec = m_input.ReadUInt16(); + format_read = true; + m_input.ReadInt32(); + Copy (block_size-12, pcm); + break; + default: + m_input.BaseStream.Seek (block_size, SeekOrigin.Current); + break; + } + if (codec != -1) + { + if (0 == codec) + { + m_format.FormatTag = 1; + } + else if (4 == codec) + { + m_format.FormatTag = 1; + m_format.BitsPerSample = 16; + } + else if (7 == codec) + { + m_format.FormatTag = 7; + } + else + throw new NotImplementedException(); + } + } + if (!format_read || 0 == Format.Channels || 0 == pcm.Length) + throw new InvalidFormatException(); + m_format.BlockAlign = (ushort)(m_format.Channels * m_format.BitsPerSample / 8); + m_format.AverageBytesPerSecond = m_format.SamplesPerSecond * m_format.BlockAlign; + pcm.Position = 0; + return pcm; + } + catch + { + pcm.Dispose(); + throw; + } + } + + byte[] m_buffer = null; + + void Copy (int block_size, Stream output) + { + if (null == m_buffer || m_buffer.Length < block_size) + m_buffer = new byte[block_size]; + if (block_size != m_input.Read (m_buffer, 0, block_size)) + throw new EndOfStreamException(); + output.Write (m_buffer, 0, block_size); + } + + #region IDisposable Members + bool _disposed = false; + public void Dispose () + { + if (!_disposed) + { + m_input.Dispose(); + _disposed = true; + } + } + #endregion + } + + [Export(typeof(AudioFormat))] + public class VocAudio : AudioFormat + { + public override string Tag { get { return "VOC"; } } + public override string Description { get { return "Creative Voice File"; } } + public override uint Signature { get { return 0x61657243; } } // 'Crea' + + public override SoundInput TryOpen (Stream file) + { + var header = new byte[0x1A]; + if (header.Length != file.Read (header, 0, header.Length)) + return null; + if (!Binary.AsciiEqual (header, 0, "Creative Voice File\x1A")) + return null; + using (var reader = new VocReader (file, header)) + { + var pcm = reader.ConvertToPcm(); + if (null == pcm) + return null; + return new RawPcmInput (pcm, reader.Format); + } + } + } +} + +