(() => 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
+ }
+}
diff --git a/ArcFormats/Cri/AudioHCA.cs b/ArcFormats/Cri/AudioHCA.cs
index 28d91d46..2794483c 100644
--- a/ArcFormats/Cri/AudioHCA.cs
+++ b/ArcFormats/Cri/AudioHCA.cs
@@ -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 ()
diff --git a/supported.html b/supported.html
index b075f61a..205d7c56 100644
--- a/supported.html
+++ b/supported.html
@@ -18,8 +18,17 @@ tr.odd td { background-color: #eee }
| *.pak | ADPACK32 | No | Active Soft | Bible Black Discipline Discipline LS Fearless] |
| *.edt | .TRUE | No |
| *.ed8 | .8Bit | No |
-| *.afs | AFS | No | PlayStation 2 | Remember11 |
+| *.afs | AFS | No | CRI |
+Iwaihime
+Phantom of Inferno
+Remember11
+ |
| *.bip | - | No |
+| *.cpk | CPK | No |
+| *.spc | - | No |
+| *.xtx | xtx | No |
+| *.hca | HCA | No |
+| *.adx | \x80\x00 | No |
| data.ami | AMI | Yes | - | Muv-Luv Amaterasu Translation data files |
| *.arc | PackFile | No | BGI/Ethornell |
11gatsu no Arcadia
@@ -545,9 +554,10 @@ Majiresu!! ~Omatase Little Wing~
Futamajo
|
| *.bmp | MWP | No |
-| *.dat+*l.dat | - GLNK | No | Studio Miris |
+ |
| *.dat+*l.dat | - GLNK | No | Studio Miris Caligula |
Itadaki Jangarian
Tetsuwan Gacchu!
+Sakimidare
|
*.bdf *.spl | - | No | Zyx | Innai Kansen series |
| *.pak | PAK | No | Debonosu Works |
@@ -721,11 +731,6 @@ Tokyo Necro
Thanatos no Koi ~In Ane Otouto Soukan~
|
| *.gps | GPS | No |
-| *.cpk | CPK | No | CRI |
-Iwaihime
- |
-| *.xtx | xtx | No |
-| *.hca | HCA | No |
Non-encrypted only