mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 13:48:57 +08:00
implemented ADX audio format.
This commit is contained in:
@@ -69,6 +69,7 @@
|
||||
<Compile Include="ArcZIP.cs" />
|
||||
<Compile Include="Cri\ArcCPK.cs" />
|
||||
<Compile Include="Cri\ArcSPC.cs" />
|
||||
<Compile Include="Cri\AudioADX.cs" />
|
||||
<Compile Include="Cri\AudioHCA.cs" />
|
||||
<Compile Include="Cri\BigEndianReader.cs" />
|
||||
<Compile Include="Cri\ImageSPC.cs" />
|
||||
|
||||
@@ -74,11 +74,4 @@ namespace GameRes.Formats.Cri
|
||||
return offset & ~(long)(block_size - 1);
|
||||
}
|
||||
}
|
||||
|
||||
[Export(typeof(AudioFormat))]
|
||||
public class AdxAudio : OggAudio
|
||||
{
|
||||
public override string Tag { get { return "ADX"; } }
|
||||
public override string Description { get { return "PS2 audio format (Ogg/Vorbis)"; } }
|
||||
}
|
||||
}
|
||||
|
||||
271
ArcFormats/Cri/AudioADX.cs
Normal file
271
ArcFormats/Cri/AudioADX.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
//! \file AudioADX.cs
|
||||
//! \date Wed Mar 09 11:21:57 2016
|
||||
//! \brief CRI MiddleWare ADPCM audio.
|
||||
//
|
||||
// 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 System.Threading;
|
||||
using GameRes.Utility;
|
||||
|
||||
namespace GameRes.Formats.Cri
|
||||
{
|
||||
[Export(typeof(AudioFormat))]
|
||||
public class AdxAudio : AudioFormat
|
||||
{
|
||||
public override string Tag { get { return "ADX"; } }
|
||||
public override string Description { get { return "CRI MiddleWare ADPCM audio"; } }
|
||||
public override uint Signature { get { return 0; } }
|
||||
|
||||
public override SoundInput TryOpen (Stream file)
|
||||
{
|
||||
uint signature = FormatCatalog.ReadSignature (file);
|
||||
if (0x80 != (signature & 0xFFFF))
|
||||
return null;
|
||||
uint header_size = Binary.BigEndian (signature & 0xFFFF0000);
|
||||
if (header_size < 0x10 || header_size >= file.Length)
|
||||
return null;
|
||||
var header = new byte[header_size];
|
||||
if (header.Length != file.Read (header, 0, header.Length))
|
||||
return null;
|
||||
if (!Binary.AsciiEqual (header, header.Length-6, "(c)CRI"))
|
||||
return null;
|
||||
|
||||
return new AdxInput (file, header);
|
||||
}
|
||||
}
|
||||
|
||||
internal class AdxInput : SoundInput
|
||||
{
|
||||
AdxReader m_reader;
|
||||
int m_data_offset;
|
||||
int m_bitrate;
|
||||
long m_position;
|
||||
int m_buffered_sample;
|
||||
int m_buffered_count;
|
||||
int m_bytes_per_sample;
|
||||
ThreadLocal<short[]> m_frame_buffer;
|
||||
|
||||
public override string SourceFormat { get { return "adx"; } }
|
||||
public override int SourceBitrate { get { return m_bitrate; } }
|
||||
|
||||
public AdxInput (Stream file, byte[] header) : base (file)
|
||||
{
|
||||
m_reader = new AdxReader (file, header);
|
||||
m_data_offset = 4 + header.Length;
|
||||
this.Format = m_reader.Format;
|
||||
this.PcmSize = m_reader.SampleCount * Format.BlockAlign;
|
||||
m_bitrate = (int)(Format.SamplesPerSecond * (file.Length-m_data_offset) * 8 / m_reader.SampleCount);
|
||||
m_frame_buffer = new ThreadLocal<short[]> (() => new short[m_reader.SamplesPerFrame * m_reader.Format.Channels]);
|
||||
m_bytes_per_sample = Format.BitsPerSample / 8;
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
{
|
||||
int total_read = 0;
|
||||
int current_sample = (int)(m_position / Format.BlockAlign);
|
||||
if (current_sample >= m_buffered_sample && current_sample < m_buffered_sample + m_buffered_count)
|
||||
{
|
||||
int src_offset = (int)(m_position % (m_frame_buffer.Value.Length * m_bytes_per_sample));
|
||||
int available = Math.Min (count, m_buffered_count * Format.BlockAlign - src_offset);
|
||||
Buffer.BlockCopy (m_frame_buffer.Value, src_offset, buffer, offset, available);
|
||||
offset += available;
|
||||
count -= available;
|
||||
total_read += available;
|
||||
m_position += available;
|
||||
}
|
||||
int bytes_per_frame = m_reader.SamplesPerFrame * m_bytes_per_sample * Format.Channels;
|
||||
while (count > 0 && m_position < PcmSize)
|
||||
{
|
||||
int frame_number = (int)(m_position / bytes_per_frame);
|
||||
m_reader.SetPosition (m_data_offset + frame_number * m_reader.FrameSize * Format.Channels);
|
||||
for (int i = 0; i < Format.Channels; ++i)
|
||||
{
|
||||
m_reader.DecodeFrame (i, m_frame_buffer.Value);
|
||||
}
|
||||
m_buffered_sample = frame_number * m_reader.SamplesPerFrame;
|
||||
m_buffered_count = Math.Min (m_reader.SampleCount - m_buffered_sample, m_reader.SamplesPerFrame);
|
||||
int available = Math.Min (count, m_buffered_count * Format.BlockAlign);
|
||||
Buffer.BlockCopy (m_frame_buffer.Value, 0, buffer, offset, available);
|
||||
offset += available;
|
||||
count -= available;
|
||||
total_read += available;
|
||||
m_position += available;
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
// FIXME
|
||||
// such implementation of seek is broken since it doesn't take into account ADPCM decoder 'history',
|
||||
// therefore CanSeek returns false.
|
||||
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_position; }
|
||||
set { m_position = Math.Max (value, 0); }
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool _adx_disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!_adx_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
m_reader.Dispose();
|
||||
m_frame_buffer.Dispose();
|
||||
}
|
||||
_adx_disposed = true;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class AdxReader : IDisposable
|
||||
{
|
||||
MsbBitStream m_input;
|
||||
WaveFormat m_format;
|
||||
int m_samples_per_frame;
|
||||
int m_prev_scale0;
|
||||
int m_prev_scale1;
|
||||
int[][] m_prev_samples;
|
||||
|
||||
public WaveFormat Format { get { return m_format; } }
|
||||
public int SamplesPerFrame { get { return m_samples_per_frame; } }
|
||||
public int SampleCount { get; private set; }
|
||||
public int FrameSize { get; private set; }
|
||||
public int BitsPerSample { get; private set; }
|
||||
|
||||
public AdxReader (Stream file, byte[] header)
|
||||
{
|
||||
FrameSize = header[1];
|
||||
BitsPerSample = header[2];
|
||||
if (header[0] != 3 || FrameSize != 0x12 || BitsPerSample != 4)
|
||||
throw new InvalidFormatException();
|
||||
m_samples_per_frame = (FrameSize - 2) * 8 / BitsPerSample;
|
||||
m_format.Channels = header[3];
|
||||
if (0 == m_format.Channels || m_format.Channels > 16)
|
||||
throw new InvalidFormatException();
|
||||
int encoding = BigEndian.ToInt16 (header, 0x0E);
|
||||
if (encoding != 0x0400)
|
||||
throw new NotSupportedException ("Not supported ADX encoding");
|
||||
|
||||
m_format.FormatTag = 1;
|
||||
m_format.SamplesPerSecond = BigEndian.ToUInt32 (header, 4);
|
||||
m_format.BitsPerSample = 16;
|
||||
m_format.BlockAlign = (ushort)(m_format.BitsPerSample * m_format.Channels / 8);
|
||||
m_format.AverageBytesPerSecond = m_format.SamplesPerSecond * m_format.BlockAlign;
|
||||
SampleCount = BigEndian.ToInt32 (header, 8);
|
||||
|
||||
int lowest_freq = BigEndian.ToUInt16 (header, 12);
|
||||
var sqrt2 = Math.Sqrt (2.0);
|
||||
var x = sqrt2 - Math.Cos (2 * Math.PI * lowest_freq / m_format.SamplesPerSecond);
|
||||
var y = sqrt2 - 1;
|
||||
var z = (x - Math.Sqrt ((x + y) * (x - y))) / y;
|
||||
|
||||
m_prev_scale0 = (int)Math.Floor (z * 8192);
|
||||
m_prev_scale1 = (int)Math.Floor (z * z * -4096);
|
||||
m_prev_samples = new int[m_format.Channels][];
|
||||
for (int i = 0; i < m_format.Channels; ++i)
|
||||
{
|
||||
m_prev_samples[i] = new int[2];
|
||||
}
|
||||
|
||||
file.Position = 4+header.Length;
|
||||
m_input = new MsbBitStream (file, true);
|
||||
}
|
||||
|
||||
/*
|
||||
public void Decode (Stream output)
|
||||
{
|
||||
int block_align = (int)m_format.BlockAlign;
|
||||
var frame = new short[m_samples_per_frame * Format.Channels];
|
||||
int sample = 0;
|
||||
while (sample < SampleCount)
|
||||
{
|
||||
int channel_offset = 0;
|
||||
for (int i = 0; i < m_format.Channels; ++i)
|
||||
{
|
||||
DecodeFrame (i, frame);
|
||||
}
|
||||
int samples_in_frame = Math.Min (SampleCount - sample, m_samples_per_frame);
|
||||
output.Write (frame, 0, samples_in_frame * block_align); // XXX convert to byte array
|
||||
sample += samples_in_frame;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public void SetPosition (long position)
|
||||
{
|
||||
m_input.Input.Position = position;
|
||||
m_input.Reset();
|
||||
}
|
||||
|
||||
public void DecodeFrame (int channel, short[] output)
|
||||
{
|
||||
int offset = channel;
|
||||
var prev_samples = m_prev_samples[channel];
|
||||
int scale = (short)m_input.GetBits (16) + 1;
|
||||
for (int i = 0; i < m_samples_per_frame; ++i)
|
||||
{
|
||||
int sample = NibbleToSigned (m_input.GetBits (BitsPerSample));
|
||||
int adjust = (m_prev_scale0 * prev_samples[0] + m_prev_scale1 * prev_samples[1]) >> 12;
|
||||
sample = Clamp16 (sample * scale + adjust);
|
||||
output[offset] = (short)sample;
|
||||
offset += m_format.Channels;
|
||||
prev_samples[1] = prev_samples[0];
|
||||
prev_samples[0] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
static int NibbleToSigned (int n)
|
||||
{
|
||||
return (n & 7) - (n & 8);
|
||||
}
|
||||
|
||||
static int Clamp16 (int sample)
|
||||
{
|
||||
if (sample > 0x7FFF)
|
||||
sample = 0x7FFF;
|
||||
else if (sample < -0x8000)
|
||||
sample = -0x8000;
|
||||
return sample;
|
||||
}
|
||||
|
||||
#region IDisposable Members
|
||||
bool _disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
m_input.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace GameRes.Formats.Cri
|
||||
public class HcaAudio : AudioFormat
|
||||
{
|
||||
public override string Tag { get { return "HCA"; } }
|
||||
public override string Description { get { return "CRI MiddleWare compressed audio"; } }
|
||||
public override string Description { get { return "CRI MiddleWare high compressed audio"; } }
|
||||
public override uint Signature { get { return 0x00414348; } } // 'HCA'
|
||||
|
||||
public HcaAudio ()
|
||||
|
||||
@@ -18,8 +18,17 @@ tr.odd td { background-color: #eee }
|
||||
<tr class="odd"><td>*.pak</td><td><tt>ADPACK32</tt></td><td>No</td><td rowspan="3">Active Soft</td><td rowspan="3">Bible Black<br/>Discipline<br/>Discipline LS<br/>Fearless]</td></tr>
|
||||
<tr class="odd"><td>*.edt</td><td><tt>.TRUE</tt></td><td>No</td></tr>
|
||||
<tr class="odd"><td>*.ed8</td><td><tt>.8Bit</tt></td><td>No</td></tr>
|
||||
<tr><td>*.afs</td><td><tt>AFS</tt></td><td>No</td><td rowspan="2">PlayStation 2</td><td rowspan="2">Remember11</td></tr>
|
||||
<tr><td>*.afs</td><td><tt>AFS</tt></td><td>No</td><td rowspan="7">CRI</td><td rowspan="7">
|
||||
Iwaihime<br/>
|
||||
Phantom of Inferno<span class="footnote">Xbox port</span><br/>
|
||||
Remember11<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.bip</td><td>-</td><td>No</td></tr>
|
||||
<tr><td>*.cpk</td><td><tt>CPK</tt></td><td>No</td></tr>
|
||||
<tr><td>*.spc</td><td>-</td><td>No</td></tr>
|
||||
<tr><td>*.xtx</td><td><tt>xtx</tt></td><td>No</td></tr>
|
||||
<tr><td>*.hca</td><td><tt>HCA</tt></td><td>No</td></tr>
|
||||
<tr><td>*.adx</td><td><tt>\x80\x00</tt></td><td>No</td></tr>
|
||||
<tr class="odd"><td>data.ami</td><td><tt>AMI</tt></td><td>Yes</td><td>-</td><td>Muv-Luv Amaterasu Translation data files</td></tr>
|
||||
<tr><td>*.arc</td><td><tt>PackFile</tt></td><td>No</td><td rowspan="2">BGI/Ethornell</td><td rowspan="2">
|
||||
11gatsu no Arcadia<br/>
|
||||
@@ -545,9 +554,10 @@ Majiresu!! ~Omatase Little Wing~<br/>
|
||||
Futamajo<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.bmp</td><td><tt>MWP</tt></td><td>No</td></tr>
|
||||
<tr><td>*.dat+*l.dat</td><td>-<br/><tt>GLNK</tt></td><td>No</td><td>Studio Miris</td><td>
|
||||
<tr><td>*.dat+*l.dat</td><td>-<br/><tt>GLNK</tt></td><td>No</td><td>Studio Miris<br/>Caligula</td><td>
|
||||
Itadaki Jangarian<br/>
|
||||
Tetsuwan Gacchu!<br/>
|
||||
Sakimidare<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.bdf<br/>*.spl</td><td>-</td><td>No</td><td>Zyx</td><td>Innai Kansen series</td></tr>
|
||||
<tr><td>*.pak</td><td><tt>PAK</tt></td><td>No</td><td>Debonosu Works</td><td>
|
||||
@@ -721,11 +731,6 @@ Tokyo Necro<br/>
|
||||
Thanatos no Koi ~In Ane Otouto Soukan~<br/>
|
||||
</td></tr>
|
||||
<tr class="odd"><td>*.gps</td><td><tt>GPS</tt></td><td>No</td></tr>
|
||||
<tr><td>*.cpk</td><td><tt>CPK</tt></td><td>No</td><td rowspan="3">CRI</td><td rowspan="3">
|
||||
Iwaihime<br/>
|
||||
</td></tr>
|
||||
<tr><td>*.xtx</td><td><tt>xtx</tt></td><td>No</td></tr>
|
||||
<tr><td>*.hca</td><td><tt>HCA</tt></td><td>No</td></tr>
|
||||
</table>
|
||||
<p><a name="note-1" class="footnote">1</a> Non-encrypted only</p>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user