diff --git a/Aaru.Compression/Aaru.Compression.csproj b/Aaru.Compression/Aaru.Compression.csproj index 77d68b8cd..c4efe1dfc 100644 --- a/Aaru.Compression/Aaru.Compression.csproj +++ b/Aaru.Compression/Aaru.Compression.csproj @@ -36,8 +36,8 @@ CS1591;CS1574 - - + + $(Version)+{chash:8} @@ -50,25 +50,25 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -76,14 +76,14 @@ - - - - - - + + + + + + - + \ No newline at end of file diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioDecoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioDecoder.cs new file mode 100644 index 000000000..44b123071 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioDecoder.cs @@ -0,0 +1,767 @@ +/** + * CUETools.Flake: pure managed FLAC audio encoder + * Copyright (c) 2009-2021 Grigory Chudov + * Based on Flake encoder, http://flake-enc.sourceforge.net/ + * Copyright (c) 2006-2009 Justin Ruggles + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +namespace CUETools.Codecs.Flake +{ + public class AudioDecoder: IAudioSource + { + int[] samplesBuffer; + int[] residualBuffer; + + byte[] _framesBuffer; + int _framesBufferLength = 0, _framesBufferOffset = 0; + long first_frame_offset; + + SeekPoint[] seek_table; + + Crc8 crc8; + FlacFrame frame; + BitReader framereader; + AudioPCMConfig pcm; + + uint min_block_size = 0; + uint max_block_size = 0; + uint min_frame_size = 0; + uint max_frame_size = 0; + + int _samplesInBuffer, _samplesBufferOffset; + long _sampleCount = 0, _sampleOffset = 0; + + bool do_crc = true; + + string _path; + Stream _IO; + + public bool DoCRC + { + get + { + return do_crc; + } + set + { + do_crc = value; + } + } + + public int[] Samples + { + get + { + return samplesBuffer; + } + } + + public AudioDecoder(DecoderSettings settings, string path, Stream IO = null) + { + m_settings = settings; + + _path = path; + _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); + + crc8 = new Crc8(); + + _framesBuffer = new byte[0x20000]; + decode_metadata(); + + frame = new FlacFrame(PCM.ChannelCount); + framereader = new BitReader(); + + //max_frame_size = 16 + ((Flake.MAX_BLOCKSIZE * PCM.BitsPerSample * PCM.ChannelCount + 1) + 7) >> 3); + if (((int)max_frame_size * PCM.BitsPerSample * PCM.ChannelCount * 2 >> 3) > _framesBuffer.Length) + { + byte[] temp = _framesBuffer; + _framesBuffer = new byte[((int)max_frame_size * PCM.BitsPerSample * PCM.ChannelCount * 2 >> 3)]; + if (_framesBufferLength > 0) + Array.Copy(temp, _framesBufferOffset, _framesBuffer, 0, _framesBufferLength); + _framesBufferOffset = 0; + } + _samplesInBuffer = 0; + + if (PCM.BitsPerSample != 16 && PCM.BitsPerSample != 24) + throw new Exception("invalid flac file"); + + samplesBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * PCM.ChannelCount]; + residualBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * PCM.ChannelCount]; + } + + public AudioDecoder(AudioPCMConfig _pcm) + { + pcm = _pcm; + crc8 = new Crc8(); + + samplesBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * PCM.ChannelCount]; + residualBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * PCM.ChannelCount]; + frame = new FlacFrame(PCM.ChannelCount); + framereader = new BitReader(); + } + + private DecoderSettings m_settings; + public IAudioDecoderSettings Settings => m_settings; + + public void Close() + { + _IO.Close(); + } + + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length + { + get + { + return _sampleCount; + } + } + + public long Remaining + { + get + { + return Length - Position; + } + } + + public long Position + { + get + { + return _sampleOffset - _samplesInBuffer; + } + set + { + if (value > _sampleCount) + throw new Exception("seeking past end of stream"); + if (value < Position || value > _sampleOffset) + { + if (seek_table != null && _IO.CanSeek) + { + int best_st = -1; + for (int st = 0; st < seek_table.Length; st++) + { + if (seek_table[st].number <= value && + (best_st == -1 || seek_table[st].number > seek_table[best_st].number)) + best_st = st; + } + if (best_st != -1) + { + _framesBufferLength = 0; + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + _IO.Position = (long)seek_table[best_st].offset + first_frame_offset; + _sampleOffset = seek_table[best_st].number; + } + } + if (value < Position) + throw new Exception("cannot seek backwards without seek table"); + } + while (value > _sampleOffset) + { + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + + fill_frames_buffer(); + if (_framesBufferLength == 0) + throw new Exception("seek failed"); + + int bytesDecoded = DecodeFrame(_framesBuffer, _framesBufferOffset, _framesBufferLength); + _framesBufferLength -= bytesDecoded; + _framesBufferOffset += bytesDecoded; + + _sampleOffset += _samplesInBuffer; + }; + int diff = _samplesInBuffer - (int)(_sampleOffset - value); + _samplesInBuffer -= diff; + _samplesBufferOffset += diff; + } + } + + public AudioPCMConfig PCM + { + get + { + return pcm; + } + } + + public string Path + { + get + { + return _path; + } + } + + unsafe void interlace(AudioBuffer buff, int offset, int count) + { + if (PCM.ChannelCount == 2) + { + fixed (int* src = &samplesBuffer[_samplesBufferOffset]) + buff.Interlace(offset, src, src + FlakeConstants.MAX_BLOCKSIZE, count); + } + else + { + for (int ch = 0; ch < PCM.ChannelCount; ch++) + fixed (int* res = &buff.Samples[offset, ch], src = &samplesBuffer[_samplesBufferOffset + ch * FlakeConstants.MAX_BLOCKSIZE]) + { + int* psrc = src; + for (int i = 0; i < count; i++) + res[i * PCM.ChannelCount] = *(psrc++); + } + } + } + + public int Read(AudioBuffer buff, int maxLength) + { + buff.Prepare(this, maxLength); + + int offset = 0; + int sampleCount = buff.Length; + + while (_samplesInBuffer < sampleCount) + { + if (_samplesInBuffer > 0) + { + interlace(buff, offset, _samplesInBuffer); + sampleCount -= _samplesInBuffer; + offset += _samplesInBuffer; + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + } + + fill_frames_buffer(); + + if (_framesBufferLength == 0) + return buff.Length = offset; + + int bytesDecoded = DecodeFrame(_framesBuffer, _framesBufferOffset, _framesBufferLength); + _framesBufferLength -= bytesDecoded; + _framesBufferOffset += bytesDecoded; + + _samplesInBuffer -= _samplesBufferOffset; // can be set by Seek, otherwise zero + _sampleOffset += _samplesInBuffer; + } + + interlace(buff, offset, sampleCount); + _samplesInBuffer -= sampleCount; + _samplesBufferOffset += sampleCount; + if (_samplesInBuffer == 0) + _samplesBufferOffset = 0; + return buff.Length = offset + sampleCount; + } + + unsafe void fill_frames_buffer() + { + if (_framesBufferLength == 0) + _framesBufferOffset = 0; + else if (_framesBufferLength < _framesBuffer.Length / 2 && _framesBufferOffset >= _framesBuffer.Length / 2) + { + fixed (byte* buff = _framesBuffer) + AudioSamples.MemCpy(buff, buff + _framesBufferOffset, _framesBufferLength); + _framesBufferOffset = 0; + } + while (_framesBufferLength < _framesBuffer.Length / 2) + { + int read = _IO.Read(_framesBuffer, _framesBufferOffset + _framesBufferLength, _framesBuffer.Length - _framesBufferOffset - _framesBufferLength); + _framesBufferLength += read; + if (read == 0) + break; + } + } + + unsafe void decode_frame_header(BitReader bitreader, FlacFrame frame) + { + int header_start = bitreader.Position; + + if (bitreader.readbits(15) != 0x7FFC) + throw new Exception("invalid frame"); + uint vbs = bitreader.readbit(); + frame.bs_code0 = (int) bitreader.readbits(4); + uint sr_code0 = bitreader.readbits(4); + frame.ch_mode = (ChannelMode)bitreader.readbits(4); + uint bps_code = bitreader.readbits(3); + if (FlakeConstants.flac_bitdepths[bps_code] != PCM.BitsPerSample) + throw new Exception("unsupported bps coding"); + uint t1 = bitreader.readbit(); // == 0????? + if (t1 != 0) + throw new Exception("unsupported frame coding"); + frame.frame_number = (int)bitreader.read_utf8(); + + // custom block size + if (frame.bs_code0 == 6) + { + frame.bs_code1 = (int)bitreader.readbits(8); + frame.blocksize = frame.bs_code1 + 1; + } + else if (frame.bs_code0 == 7) + { + frame.bs_code1 = (int)bitreader.readbits(16); + frame.blocksize = frame.bs_code1 + 1; + } + else + frame.blocksize = FlakeConstants.flac_blocksizes[frame.bs_code0]; + + // custom sample rate + if (sr_code0 < 1 || sr_code0 > 11) + { + // sr_code0 == 12 -> sr == bitreader.readbits(8) * 1000; + // sr_code0 == 13 -> sr == bitreader.readbits(16); + // sr_code0 == 14 -> sr == bitreader.readbits(16) * 10; + throw new Exception("invalid sample rate mode"); + } + + int frame_channels = (int)frame.ch_mode + 1; + if (frame_channels > 11) + throw new Exception("invalid channel mode"); + if (frame_channels == 2 || frame_channels > 8) // Mid/Left/Right Side Stereo + frame_channels = 2; + else + frame.ch_mode = ChannelMode.NotStereo; + if (frame_channels != PCM.ChannelCount) + throw new Exception("invalid channel mode"); + + // CRC-8 of frame header + byte crc = do_crc ? crc8.ComputeChecksum(bitreader.Buffer, header_start, bitreader.Position - header_start) : (byte)0; + frame.crc8 = (byte)bitreader.readbits(8); + if (do_crc && frame.crc8 != crc) + throw new Exception("header crc mismatch"); + } + + unsafe void decode_subframe_constant(BitReader bitreader, FlacFrame frame, int ch) + { + int obits = frame.subframes[ch].obits; + frame.subframes[ch].best.residual[0] = bitreader.readbits_signed(obits); + } + + unsafe void decode_subframe_verbatim(BitReader bitreader, FlacFrame frame, int ch) + { + int obits = frame.subframes[ch].obits; + for (int i = 0; i < frame.blocksize; i++) + frame.subframes[ch].best.residual[i] = bitreader.readbits_signed(obits); + } + + unsafe void decode_residual(BitReader bitreader, FlacFrame frame, int ch) + { + // rice-encoded block + // coding method + frame.subframes[ch].best.rc.coding_method = (int)bitreader.readbits(2); // ????? == 0 + if (frame.subframes[ch].best.rc.coding_method != 0 && frame.subframes[ch].best.rc.coding_method != 1) + throw new Exception("unsupported residual coding"); + // partition order + frame.subframes[ch].best.rc.porder = (int)bitreader.readbits(4); + if (frame.subframes[ch].best.rc.porder > 8) + throw new Exception("invalid partition order"); + int psize = frame.blocksize >> frame.subframes[ch].best.rc.porder; + int res_cnt = psize - frame.subframes[ch].best.order; + + int rice_len = 4 + frame.subframes[ch].best.rc.coding_method; + // residual + int j = frame.subframes[ch].best.order; + int* r = frame.subframes[ch].best.residual + j; + for (int p = 0; p < (1 << frame.subframes[ch].best.rc.porder); p++) + { + if (p == 1) res_cnt = psize; + int n = Math.Min(res_cnt, frame.blocksize - j); + + int k = frame.subframes[ch].best.rc.rparams[p] = (int)bitreader.readbits(rice_len); + if (k == (1 << rice_len) - 1) + { + k = frame.subframes[ch].best.rc.esc_bps[p] = (int)bitreader.readbits(5); + for (int i = n; i > 0; i--) + *(r++) = bitreader.readbits_signed((int)k); + } + else + { + bitreader.read_rice_block(n, (int)k, r); + r += n; + } + j += n; + } + } + + unsafe void decode_subframe_fixed(BitReader bitreader, FlacFrame frame, int ch) + { + // warm-up samples + int obits = frame.subframes[ch].obits; + for (int i = 0; i < frame.subframes[ch].best.order; i++) + frame.subframes[ch].best.residual[i] = bitreader.readbits_signed(obits); + + // residual + decode_residual(bitreader, frame, ch); + } + + unsafe void decode_subframe_lpc(BitReader bitreader, FlacFrame frame, int ch) + { + // warm-up samples + int obits = frame.subframes[ch].obits; + for (int i = 0; i < frame.subframes[ch].best.order; i++) + frame.subframes[ch].best.residual[i] = bitreader.readbits_signed(obits); + + // LPC coefficients + frame.subframes[ch].best.cbits = (int)bitreader.readbits(4) + 1; // lpc_precision + if (frame.subframes[ch].best.cbits >= 16) + throw new Exception("cbits >= 16"); + frame.subframes[ch].best.shift = bitreader.readbits_signed(5); + if (frame.subframes[ch].best.shift < 0) + throw new Exception("negative shift"); + for (int i = 0; i < frame.subframes[ch].best.order; i++) + frame.subframes[ch].best.coefs[i] = bitreader.readbits_signed(frame.subframes[ch].best.cbits); + + // residual + decode_residual(bitreader, frame, ch); + } + + unsafe void decode_subframes(BitReader bitreader, FlacFrame frame) + { + fixed (int *r = residualBuffer, s = samplesBuffer) + for (int ch = 0; ch < PCM.ChannelCount; ch++) + { + // subframe header + uint t1 = bitreader.readbit(); // ?????? == 0 + if (t1 != 0) + throw new Exception("unsupported subframe coding (ch == " + ch.ToString() + ")"); + int type_code = (int)bitreader.readbits(6); + frame.subframes[ch].wbits = (int)bitreader.readbit(); + if (frame.subframes[ch].wbits != 0) + frame.subframes[ch].wbits += (int)bitreader.read_unary(); + + frame.subframes[ch].obits = PCM.BitsPerSample - frame.subframes[ch].wbits; + switch (frame.ch_mode) + { + case ChannelMode.MidSide: frame.subframes[ch].obits += ch; break; + case ChannelMode.LeftSide: frame.subframes[ch].obits += ch; break; + case ChannelMode.RightSide: frame.subframes[ch].obits += 1 - ch; break; + } + + frame.subframes[ch].best.type = (SubframeType)type_code; + frame.subframes[ch].best.order = 0; + + if ((type_code & (uint)SubframeType.LPC) != 0) + { + frame.subframes[ch].best.order = (type_code - (int)SubframeType.LPC) + 1; + frame.subframes[ch].best.type = SubframeType.LPC; + } + else if ((type_code & (uint)SubframeType.Fixed) != 0) + { + frame.subframes[ch].best.order = (type_code - (int)SubframeType.Fixed); + frame.subframes[ch].best.type = SubframeType.Fixed; + } + + frame.subframes[ch].best.residual = r + ch * FlakeConstants.MAX_BLOCKSIZE; + frame.subframes[ch].samples = s + ch * FlakeConstants.MAX_BLOCKSIZE; + + // subframe + switch (frame.subframes[ch].best.type) + { + case SubframeType.Constant: + decode_subframe_constant(bitreader, frame, ch); + break; + case SubframeType.Verbatim: + decode_subframe_verbatim(bitreader, frame, ch); + break; + case SubframeType.Fixed: + decode_subframe_fixed(bitreader, frame, ch); + break; + case SubframeType.LPC: + decode_subframe_lpc(bitreader, frame, ch); + break; + default: + throw new Exception("invalid subframe type"); + } + } + } + + unsafe void restore_samples_fixed(FlacFrame frame, int ch) + { + FlacSubframeInfo sub = frame.subframes[ch]; + + AudioSamples.MemCpy(sub.samples, sub.best.residual, sub.best.order); + int* data = sub.samples + sub.best.order; + int* residual = sub.best.residual + sub.best.order; + int data_len = frame.blocksize - sub.best.order; + int s0, s1, s2; + switch (sub.best.order) + { + case 0: + AudioSamples.MemCpy(data, residual, data_len); + break; + case 1: + s1 = data[-1]; + for (int i = data_len; i > 0; i--) + { + s1 += *(residual++); + *(data++) = s1; + } + //data[i] = residual[i] + data[i - 1]; + break; + case 2: + s2 = data[-2]; + s1 = data[-1]; + for (int i = data_len; i > 0; i--) + { + s0 = *(residual++) + (s1 << 1) - s2; + *(data++) = s0; + s2 = s1; + s1 = s0; + } + //data[i] = residual[i] + data[i - 1] * 2 - data[i - 2]; + break; + case 3: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + (((data[i - 1] - data[i - 2]) << 1) + (data[i - 1] - data[i - 2])) + data[i - 3]; + break; + case 4: + for (int i = 0; i < data_len; i++) + data[i] = residual[i] + ((data[i - 1] + data[i - 3]) << 2) - ((data[i - 2] << 2) + (data[i - 2] << 1)) - data[i - 4]; + break; + } + } + + unsafe void restore_samples_lpc(FlacFrame frame, int ch) + { + FlacSubframeInfo sub = frame.subframes[ch]; + ulong csum = 0; + fixed (int* coefs = sub.best.coefs) + { + for (int i = sub.best.order; i > 0; i--) + csum += (ulong)Math.Abs(coefs[i - 1]); + if ((csum << sub.obits) >= 1UL << 32) + lpc.decode_residual_long(sub.best.residual, sub.samples, frame.blocksize, sub.best.order, coefs, sub.best.shift); + else + lpc.decode_residual(sub.best.residual, sub.samples, frame.blocksize, sub.best.order, coefs, sub.best.shift); + } + } + + unsafe void restore_samples(FlacFrame frame) + { + for (int ch = 0; ch < PCM.ChannelCount; ch++) + { + switch (frame.subframes[ch].best.type) + { + case SubframeType.Constant: + AudioSamples.MemSet(frame.subframes[ch].samples, frame.subframes[ch].best.residual[0], frame.blocksize); + break; + case SubframeType.Verbatim: + AudioSamples.MemCpy(frame.subframes[ch].samples, frame.subframes[ch].best.residual, frame.blocksize); + break; + case SubframeType.Fixed: + restore_samples_fixed(frame, ch); + break; + case SubframeType.LPC: + restore_samples_lpc(frame, ch); + break; + } + if (frame.subframes[ch].wbits != 0) + { + int* s = frame.subframes[ch].samples; + int x = (int) frame.subframes[ch].wbits; + for (int i = frame.blocksize; i > 0; i--) + *(s++) <<= x; + } + } + if (frame.ch_mode != ChannelMode.NotStereo) + { + int* l = frame.subframes[0].samples; + int* r = frame.subframes[1].samples; + switch (frame.ch_mode) + { + case ChannelMode.LeftRight: + break; + case ChannelMode.MidSide: + for (int i = frame.blocksize; i > 0; i--) + { + int mid = *l; + int side = *r; + mid <<= 1; + mid |= (side & 1); /* i.e. if 'side' is odd... */ + *(l++) = (mid + side) >> 1; + *(r++) = (mid - side) >> 1; + } + break; + case ChannelMode.LeftSide: + for (int i = frame.blocksize; i > 0; i--) + { + int _l = *(l++), _r = *r; + *(r++) = _l - _r; + } + break; + case ChannelMode.RightSide: + for (int i = frame.blocksize; i > 0; i--) + *(l++) += *(r++); + break; + } + } + } + + public unsafe int DecodeFrame(byte[] buffer, int pos, int len) + { + fixed (byte* buf = buffer) + { + framereader.Reset(buf, pos, len); + decode_frame_header(framereader, frame); + decode_subframes(framereader, frame); + framereader.flush(); + ushort crc_1 = framereader.get_crc16(); + ushort crc_2 = framereader.read_ushort(); + if (do_crc && crc_1 != crc_2) + throw new Exception("frame crc mismatch"); + restore_samples(frame); + _samplesInBuffer = frame.blocksize; + return framereader.Position - pos; + } + } + + + bool skip_bytes(int bytes) + { + for (int j = 0; j < bytes; j++) + if (0 == _IO.Read(_framesBuffer, 0, 1)) + return false; + return true; + } + + unsafe void decode_metadata() + { + byte x; + int i, id; + //bool first = true; + byte[] FLAC__STREAM_SYNC_STRING = new byte[] { (byte)'f', (byte)'L', (byte)'a', (byte)'C' }; + byte[] ID3V2_TAG_ = new byte[] { (byte)'I', (byte)'D', (byte)'3' }; + + for (i = id = 0; i < 4; ) + { + if (_IO.Read(_framesBuffer, 0, 1) == 0) + throw new Exception("FLAC stream not found"); + x = _framesBuffer[0]; + if (x == FLAC__STREAM_SYNC_STRING[i]) + { + //first = true; + i++; + id = 0; + continue; + } + if (id < 3 && x == ID3V2_TAG_[id]) + { + id++; + i = 0; + if (id == 3) + { + if (!skip_bytes(3)) + throw new Exception("FLAC stream not found"); + int skip = 0; + for (int j = 0; j < 4; j++) + { + if (0 == _IO.Read(_framesBuffer, 0, 1)) + throw new Exception("FLAC stream not found"); + skip <<= 7; + skip |= ((int)_framesBuffer[0] & 0x7f); + } + if (!skip_bytes(skip)) + throw new Exception("FLAC stream not found"); + } + continue; + } + id = 0; + if (x == 0xff) /* MAGIC NUMBER for the first 8 frame sync bits */ + { + do + { + if (_IO.Read(_framesBuffer, 0, 1) == 0) + throw new Exception("FLAC stream not found"); + x = _framesBuffer[0]; + } while (x == 0xff); + if (x >> 2 == 0x3e) /* MAGIC NUMBER for the last 6 sync bits */ + { + //_IO.Position -= 2; + // state = frame + throw new Exception("headerless file unsupported"); + } + } + throw new Exception("FLAC stream not found"); + } + + do + { + fill_frames_buffer(); + fixed (byte* buf = _framesBuffer) + { + BitReader bitreader = new BitReader(buf, _framesBufferOffset, _framesBufferLength - _framesBufferOffset); + bool is_last = bitreader.readbit() != 0; + MetadataType type = (MetadataType)bitreader.readbits(7); + int len = (int)bitreader.readbits(24); + + if (type == MetadataType.StreamInfo) + { + const int FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN = 16; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN = 16; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN = 24; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN = 24; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN = 20; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN = 3; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN = 5; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN = 36; /* bits */ + const int FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN = 128; /* bits */ + + min_block_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MIN_BLOCK_SIZE_LEN); + max_block_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MAX_BLOCK_SIZE_LEN); + min_frame_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MIN_FRAME_SIZE_LEN); + max_frame_size = bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_MAX_FRAME_SIZE_LEN); + int sample_rate = (int)bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_SAMPLE_RATE_LEN); + int channels = 1 + (int)bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_CHANNELS_LEN); + int bits_per_sample = 1 + (int)bitreader.readbits(FLAC__STREAM_METADATA_STREAMINFO_BITS_PER_SAMPLE_LEN); + pcm = new AudioPCMConfig(bits_per_sample, channels, sample_rate); + _sampleCount = (long)bitreader.readbits64(FLAC__STREAM_METADATA_STREAMINFO_TOTAL_SAMPLES_LEN); + bitreader.skipbits(FLAC__STREAM_METADATA_STREAMINFO_MD5SUM_LEN); + } + else if (type == MetadataType.Seektable) + { + int num_entries = len / 18; + seek_table = new SeekPoint[num_entries]; + for (int e = 0; e < num_entries; e++) + { + seek_table[e].number = bitreader.read_long(); + seek_table[e].offset = bitreader.read_long(); + seek_table[e].framesize = (int)bitreader.read_ushort(); + } + } + if (_framesBufferLength < 4 + len) + { + _IO.Position += 4 + len - _framesBufferLength; + _framesBufferLength = 0; + } + else + { + _framesBufferLength -= 4 + len; + _framesBufferOffset += 4 + len; + } + if (is_last) + break; + } + } while (true); + first_frame_offset = _IO.Position - _framesBufferLength; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioEncoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioEncoder.cs new file mode 100644 index 000000000..54f42e062 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/AudioEncoder.cs @@ -0,0 +1,2402 @@ +/** + * CUETools.Flake: pure managed FLAC audio encoder + * Copyright (c) 2009-2021 Grigory Chudov + * Based on Flake encoder, http://flake-enc.sourceforge.net/ + * Copyright (c) 2006-2009 Justin Ruggles + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#define NOINTEROP +#define VARIANT1 + +using System; +using System.ComponentModel; +using System.Text; +using System.IO; +using System.Collections.Generic; +using System.Security.Cryptography; +#if INTEROP +using System.Runtime.InteropServices; +#endif +using CUETools.Codecs; +using Newtonsoft.Json; + +namespace CUETools.Codecs.Flake +{ + public class AudioEncoder : IAudioDest + { + Stream _IO = null; + string _path; + long _position; + + // number of audio channels + // set by user prior to calling flake_encode_init + // valid values are 1 to 8 + int channels, ch_code; + + // audio sample rate in Hz + // set by user prior to calling flake_encode_init + int sr_code0, sr_code1; + + // sample size in bits + // set by user prior to calling flake_encode_init + // only 16-bit is currently supported + int bps_code; + + // total stream samples + // set by user prior to calling flake_encode_init + // if 0, stream length is unknown + int sample_count = -1; + + FlakeEncodeParams eparams; + + // maximum frame size in bytes + // set by flake_encode_init + // this can be used to allocate memory for output + int max_frame_size; + + byte[] frame_buffer = null; + + int frame_count = 0; + + long first_frame_offset = 0; + +#if INTEROP + TimeSpan _userProcessorTime; +#endif + + // header bytes + // allocated by flake_encode_init and freed by flake_encode_close + byte[] header; + + int[] samplesBuffer; + int[] verifyBuffer; + int[] residualBuffer; + float[] windowBuffer; + double[] windowScale; + LpcWindowSection[, ,] windowSections; + + WindowFunction[] windowType; + int samplesInBuffer = 0; + + int m_blockSize = 0; + int _totalSize = 0; + int _windowsize = 0, _windowcount = 0; + + Crc8 crc8; + MD5 md5; + + FlacFrame frame; + AudioDecoder verify; + + SeekPoint[] seek_table; + int seek_table_offset = -1; + + bool inited = false; + + public AudioEncoder(EncoderSettings settings, string path, Stream IO = null) + { + m_settings = settings.Clone() as EncoderSettings; + m_settings.Validate(); + + //if (Settings.PCM.BitsPerSample != 16) + // throw new Exception("Bits per sample must be 16."); + //if (Settings.PCM.ChannelCount != 2) + // throw new Exception("ChannelCount must be 2."); + + channels = Settings.PCM.ChannelCount; + + // flake_validate_params + + _path = path; + _IO = IO; + + samplesBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)]; + residualBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * (channels == 2 ? 10 : channels + 1)]; + windowBuffer = new float[FlakeConstants.MAX_BLOCKSIZE * 2 * lpc.MAX_LPC_WINDOWS]; + windowScale = new double[lpc.MAX_LPC_WINDOWS]; + windowType = new WindowFunction[lpc.MAX_LPC_WINDOWS]; + windowSections = new LpcWindowSection[12, lpc.MAX_LPC_WINDOWS, lpc.MAX_LPC_SECTIONS]; + + eparams.flake_set_defaults(m_settings); + + crc8 = new Crc8(); + frame = new FlacFrame(channels * 2); + } + + public int TotalSize + { + get + { + return _totalSize; + } + } + + EncoderSettings m_settings; + + public IAudioEncoderSettings Settings => m_settings; + +#if INTEROP + [DllImport("kernel32.dll")] + static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, out long lpExitTime, out long lpKernelTime, out long lpUserTime); + [DllImport("kernel32.dll")] + static extern IntPtr GetCurrentThread(); +#endif + + void DoClose() + { + if (inited) + { + while (samplesInBuffer > 0) + { + m_blockSize = samplesInBuffer; + output_frame(); + } + + if (_IO.CanSeek) + { + if (sample_count <= 0 && _position != 0) + { + BitWriter bitwriter = new BitWriter(header, 0, 4); + bitwriter.writebits(32, (int)_position); + bitwriter.flush(); + _IO.Position = 22; + _IO.Write(header, 0, 4); + } + + if (md5 != null) + { + md5.TransformFinalBlock(frame_buffer, 0, 0); + _IO.Position = 26; + _IO.Write(md5.Hash, 0, md5.Hash.Length); + } + + if (seek_table != null) + { + _IO.Position = seek_table_offset; + int len = write_seekpoints(header, 0, 0); + _IO.Write(header, 4, len - 4); + } + } + _IO.Close(); + inited = false; + } + +#if INTEROP + long fake, KernelStart, UserStart; + GetThreadTimes(GetCurrentThread(), out fake, out fake, out KernelStart, out UserStart); + _userProcessorTime = new TimeSpan(UserStart); +#endif + } + + public void Close() + { + DoClose(); + if (sample_count > 0 && _position != sample_count) + throw new Exception(Properties.Resources.ExceptionSampleCount); + } + + public void Delete() + { + if (inited) + { + _IO.Close(); + inited = false; + } + + if (_path != "") + File.Delete(_path); + } + + public long Position + { + get + { + return _position; + } + } + + public long FinalSampleCount + { + set { sample_count = (int)value; } + } + + public OrderMethod OrderMethod + { + get { return eparams.order_method; } + set { eparams.order_method = value; } + } + + public int DevelopmentMode + { + get { return eparams.development_mode; } + set { eparams.development_mode = value; } + } + + public bool DoSeekTable + { + get { return eparams.do_seektable; } + set { eparams.do_seektable = value; } + } + + public int VBRMode + { + get { return eparams.variable_block_size; } + set { eparams.variable_block_size = value; } + } + + public TimeSpan UserProcessorTime + { + get + { +#if INTEROP + return _userProcessorTime; +#else + return new TimeSpan(0); +#endif + } + } + + unsafe int get_wasted_bits(int* signal, int samples) + { + int i, shift; + int x = 0; + + for (i = 0; i < samples && 0 == (x & 1); i++) + x |= signal[i]; + + if (x == 0) + { + shift = 0; + } + else + { + for (shift = 0; 0 == (x & 1); shift++) + x >>= 1; + } + + if (shift > 0) + { + for (i = 0; i < samples; i++) + signal[i] >>= shift; + } + + return shift; + } + + /// + /// Copy channel-interleaved input samples into separate subframes + /// + /// + /// + /// + unsafe void copy_samples(int[,] samples, int pos, int block) + { + fixed (int* fsamples = samplesBuffer, src = &samples[pos, 0]) + { + if (channels == 2) + { + if (m_settings.StereoMethod == StereoMethod.Independent) + AudioSamples.Deinterlace(fsamples + samplesInBuffer, fsamples + FlakeConstants.MAX_BLOCKSIZE + samplesInBuffer, src, block); + else + { + int* left = fsamples + samplesInBuffer; + int* right = left + FlakeConstants.MAX_BLOCKSIZE; + int* leftM = right + FlakeConstants.MAX_BLOCKSIZE; + int* rightM = leftM + FlakeConstants.MAX_BLOCKSIZE; + for (int i = 0; i < block; i++) + { + int l = src[2 * i]; + int r = src[2 * i + 1]; + left[i] = l; + right[i] = r; + leftM[i] = (l + r) >> 1; + rightM[i] = l - r; + } + } + } + else + for (int ch = 0; ch < channels; ch++) + { + int* psamples = fsamples + ch * FlakeConstants.MAX_BLOCKSIZE + samplesInBuffer; + for (int i = 0; i < block; i++) + psamples[i] = src[i * channels + ch]; + } + } + samplesInBuffer += block; + } + + //unsafe static void channel_decorrelation(int* leftS, int* rightS, int *leftM, int *rightM, int blocksize) + //{ + // for (int i = 0; i < blocksize; i++) + // { + // leftM[i] = (leftS[i] + rightS[i]) >> 1; + // rightM[i] = leftS[i] - rightS[i]; + // } + //} + + unsafe void encode_residual_verbatim(int* res, int* smp, uint n) + { + AudioSamples.MemCpy(res, smp, (int) n); + } + + unsafe static ulong encode_residual_fixed_partition(int* res, int* smp, int* end, int order, int* last_errors) + { + ulong sum = 0UL; + switch (order) + { + case 0: + { + while (smp < end) + { + int error = *(res++) = *(smp++); + sum += (uint)((error << 1) ^ (error >> 31)); + } + break; + } + case 1: + { + int last_error_0 = last_errors[0]; + while (smp < end) + { + int error, save; + error = *(smp++); save = error; + error -= last_error_0; *(res++) = error; last_error_0 = save; + sum += (uint)((error << 1) ^ (error >> 31)); + } + last_errors[0] = last_error_0; + break; + } + case 2: + { + int last_error_0 = last_errors[0], last_error_1 = last_errors[1]; + while (smp < end) + { + int error, save; + error = *(smp++); save = error; + error -= last_error_0; last_error_0 = save; save = error; + error -= last_error_1; *(res++) = error; last_error_1 = save; + sum += (uint)((error << 1) ^ (error >> 31)); + } + last_errors[0] = last_error_0; last_errors[1] = last_error_1; ; + break; + } + case 3: + { + int last_error_0 = last_errors[0], last_error_1 = last_errors[1], last_error_2 = last_errors[2]; + while (smp < end) + { + int error, save; + error = *(smp++); save = error; + error -= last_error_0; last_error_0 = save; save = error; + error -= last_error_1; last_error_1 = save; save = error; + error -= last_error_2; *(res++) = error; last_error_2 = save; + sum += (uint)((error << 1) ^ (error >> 31)); + } + last_errors[0] = last_error_0; last_errors[1] = last_error_1; last_errors[2] = last_error_2; + break; + } + case 4: + { + int last_error_0 = last_errors[0], last_error_1 = last_errors[1], last_error_2 = last_errors[2], last_error_3 = last_errors[3]; + while (smp < end) + { + int error, save; + error = *(smp++); save = error; + error -= last_error_0; last_error_0 = save; save = error; + error -= last_error_1; last_error_1 = save; save = error; + error -= last_error_2; last_error_2 = save; save = error; + error -= last_error_3; *(res++) = error; last_error_3 = save; + sum += (uint)((error << 1) ^ (error >> 31)); + } + last_errors[0] = last_error_0; last_errors[1] = last_error_1; last_errors[2] = last_error_2; last_errors[3] = last_error_3; + break; + } + default: + throw new ArgumentOutOfRangeException(); + } + return sum; + } + + unsafe static void encode_residual_fixed(int* res, int* smp, int n, int order, ulong* sums, int pmax) + { + int* last_errors = stackalloc int[4]; + int* end = smp + n; + int* seg_end = smp + (n >> pmax); + + if (order > 4) + throw new ArgumentOutOfRangeException(); + + for (int i = 0; i < order; i++) + { + int* next_errors = stackalloc int[4]; + next_errors[0] = *(res++) = *(smp++); + for (int j = 0; j < i; j++) + next_errors[j + 1] = next_errors[j] - last_errors[j]; + for (int j = 0; j <= i; j++) + last_errors[j] = next_errors[j]; + } + + while (smp < end) + { + *(sums++) = encode_residual_fixed_partition(res, smp, seg_end, order, last_errors); + res += seg_end - smp; + smp = seg_end; + seg_end += n >> pmax; + } + } + +#if XXX + unsafe static int encode_residual_fixed_estimate_best_order(int* res, int* smp, int n, int order) + { + int next_error_0, next_error_1, next_error_2, next_error_3, next_error_4; + int last_error_0, last_error_1, last_error_2, last_error_3; + int* end = smp + n; + ulong total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0; + + if (order == 0) + { + AudioSamples.MemCpy(res, smp, n); + return 0; + } + + next_error_0 = *(res++) = *(smp++); + last_error_0 = next_error_0; + + if (order == 1) + { + while (smp < end) + { + next_error_0 = *(smp++); + next_error_1 = next_error_0 - last_error_0; + + last_error_0 = next_error_0; + + total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31)); + total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31)); + + *(res++) = (int)next_error_1; + } + + if ((total_error_0 < total_error_1)) + return 0; + return 1; + } + + next_error_0 = *(res++) = *(smp++); + next_error_1 = next_error_0 - last_error_0; + last_error_0 = next_error_0; + last_error_1 = next_error_1; + + if (order == 2) + { + while (smp < end) + { + next_error_0 = *(smp++); + next_error_1 = next_error_0 - last_error_0; + next_error_2 = next_error_1 - last_error_1; + + last_error_0 = next_error_0; + last_error_1 = next_error_1; + + total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31)); + total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31)); + total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31)); + + *(res++) = (int)next_error_2; + } + + if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2)) + return 0; + else if ((total_error_1 < total_error_2)) + return 1; + return 2; + } + + next_error_0 = *(res++) = *(smp++); + next_error_1 = next_error_0 - last_error_0; + next_error_2 = next_error_1 - last_error_1; + last_error_0 = next_error_0; + last_error_1 = next_error_1; + last_error_2 = next_error_2; + + if (order == 3) + { + while (smp < end) + { + next_error_0 = *(smp++); + next_error_1 = next_error_0 - last_error_0; + next_error_2 = next_error_1 - last_error_1; + next_error_3 = next_error_2 - last_error_2; + + last_error_0 = next_error_0; + last_error_1 = next_error_1; + last_error_2 = next_error_2; + + total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31)); + total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31)); + total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31)); + total_error_3 += (ulong)((next_error_3 << 1) ^ (next_error_3 >> 31)); + + *(res++) = (int)next_error_3; + } + + if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2) & (total_error_0 < total_error_3)) + return 0; + else if ((total_error_1 < total_error_2) & (total_error_1 < total_error_3)) + return 1; + else if ((total_error_2 < total_error_3)) + return 2; + return 3; + } + + next_error_0 = *(res++) = *(smp++); + next_error_1 = next_error_0 - last_error_0; + next_error_2 = next_error_1 - last_error_1; + next_error_3 = next_error_2 - last_error_2; + last_error_0 = next_error_0; + last_error_1 = next_error_1; + last_error_2 = next_error_2; + last_error_3 = next_error_3; + + if (order == 4) + { + while (smp < end) + { + next_error_0 = *(smp++); + next_error_1 = next_error_0 - last_error_0; + next_error_2 = next_error_1 - last_error_1; + next_error_3 = next_error_2 - last_error_2; + next_error_4 = next_error_3 - last_error_3; + + last_error_0 = next_error_0; + last_error_1 = next_error_1; + last_error_2 = next_error_2; + last_error_3 = next_error_3; + + total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31)); + total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31)); + total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31)); + total_error_3 += (ulong)((next_error_3 << 1) ^ (next_error_3 >> 31)); + total_error_4 += (ulong)((next_error_4 << 1) ^ (next_error_4 >> 31)); + + *(res++) = (int)next_error_4; + } + + if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2) & (total_error_0 < total_error_3) & (total_error_0 < total_error_4)) + return 0; + else if ((total_error_1 < total_error_2) & (total_error_1 < total_error_3) & (total_error_1 < total_error_4)) + return 1; + else if ((total_error_2 < total_error_3) & (total_error_2 < total_error_4)) + return 2; + else if (total_error_3 < total_error_4) + return 3; + return 4; + } + + throw new ArgumentOutOfRangeException(); + } +#endif + static unsafe uint calc_optimal_rice_params(int porder, int* parm, ulong* sums, uint n, uint pred_order, ref int method) + { + uint part = (1U << porder); + uint cnt = (n >> porder) - pred_order; + int maxK = method > 0 ? 30 : FlakeConstants.MAX_RICE_PARAM; + int k = cnt > 0 ? Math.Min(maxK, BitReader.log2i(sums[0] / cnt)) : 0; + int realMaxK0 = k; + ulong all_bits = cnt * ((uint)k + 1U) + (sums[0] >> k); + parm[0] = k; + cnt = (n >> porder); + int logcnt = BitReader.log2i(cnt); + if (cnt == 1 << logcnt) + { + for (uint i = 1; i < part; i++) + { + ulong s = sums[i]; + ulong u = s >> logcnt; + k = u >> maxK != 0 ? maxK : BitReader.log2i((uint)u); + realMaxK0 = Math.Max(realMaxK0, k); + all_bits += ((uint)k << logcnt) + (s >> k); + parm[i] = k; + } + } + else + { + for (uint i = 1; i < part; i++) + { + ulong s = sums[i]; + ulong u = s / cnt; + k = u >> maxK != 0 ? maxK : BitReader.log2i((uint)u); + realMaxK0 = Math.Max(realMaxK0, k); + all_bits += cnt * (uint)k + (s >> k); + parm[i] = k; + } + } + all_bits += cnt * (part - 1U); + method = realMaxK0 > FlakeConstants.MAX_RICE_PARAM ? 1 : 0; + return (uint)all_bits + ((4U + (uint)method) * part); + } + + static unsafe void calc_lower_sums(int pmin, int pmax, ulong* sums) + { + for (int i = pmax - 1; i >= pmin; i--) + { + for (int j = 0; j < (1 << i); j++) + { + sums[i * FlakeConstants.MAX_PARTITIONS + j] = + sums[(i + 1) * FlakeConstants.MAX_PARTITIONS + 2 * j] + + sums[(i + 1) * FlakeConstants.MAX_PARTITIONS + 2 * j + 1]; + } + } + } + + static unsafe uint calc_rice_params_sums(RiceContext rc, int pmin, int pmax, ulong* sums, uint n, uint pred_order, int bps) + { + int* parm = stackalloc int[(pmax + 1) * FlakeConstants.MAX_PARTITIONS]; + //uint* bits = stackalloc uint[FlakeConstants.MAX_PARTITION_ORDER]; + + //assert(pmin >= 0 && pmin <= FlakeConstants.MAX_PARTITION_ORDER); + //assert(pmax >= 0 && pmax <= FlakeConstants.MAX_PARTITION_ORDER); + //assert(pmin <= pmax); + + // sums for lower levels + calc_lower_sums(pmin, pmax, sums); + + uint opt_bits = AudioSamples.UINT32_MAX; + int opt_porder = pmin; + int opt_method = 0; + for (int i = pmin; i <= pmax; i++) + { + int method = bps > 16 ? 1 : 0; + uint bits = calc_optimal_rice_params(i, parm + i * FlakeConstants.MAX_PARTITIONS, sums + i * FlakeConstants.MAX_PARTITIONS, n, pred_order, ref method); + if (bits <= opt_bits) + { + opt_bits = bits; + opt_porder = i; + opt_method = method; + } + } + + rc.porder = opt_porder; + rc.coding_method = opt_method; + fixed (int* rparms = rc.rparams) + AudioSamples.MemCpy(rparms, parm + opt_porder * FlakeConstants.MAX_PARTITIONS, (1 << opt_porder)); + + return opt_bits; + } + + static int get_max_p_order(int max_porder, int n, int order) + { + int porder = Math.Min(max_porder, BitReader.log2i(n ^ (n - 1))); + if (order > 0) + porder = Math.Min(porder, BitReader.log2i(n / order)); + return porder; + } + +// private static int[,] best_x = new int[14,8193]; + private static int[][] good_x = new int[][] { +new int[] {}, // 0 +new int[] { // 1 +0x03,0x01,0x00,0x02 +}, +new int[] {// 2 +0x01,0x07,0x06,0x02, 0x03,0x04,0x00,0x05 +}, +new int[] { // 3 +0x0b,0x0f,0x0e,0x0d, 0x03,0x01,0x05,0x02 +}, +new int[] { //4 +0x17,0x09,0x03,0x0a, 0x06,0x1d,0x1f,0x05, 0x1c,0x0d,0x07,0x0c, +}, +new int[] { // 5 +0x2b,0x3d,0x37,0x07, 0x11,0x15,0x36,0x3f, +}, +new int[] { // 6 +0x6b,0x15,0x7e,0x31, 0x07,0x1a,0x29,0x26, 0x5d,0x23,0x6f,0x19, 0x56,0x75 +}, +new int[] { // 7 +0xdb,0xef,0xb5,0x47, 0xee,0x63,0x0b,0xfd, 0x31,0xbe,0xed,0x33, 0xff,0xfb,0xd6,0xbb +}, +new int[] { // 8 +0x1bb,0x1c7,0x069,0x087, 0x1fd,0x16e,0x095,0x1de, 0x066,0x071,0x055,0x09a, +}, +new int[] { // 9 +0x36b,0x3bd,0x097,0x0c3, 0x0e3,0x0b1,0x107,0x2de, 0x3ef,0x2fb,0x3d5,0x139 +}, +new int[] { // 10 +//0x0e3,0x199,0x383,0x307, 0x1e3,0x01f,0x269,0x0f1, 0x266,0x03f,0x2cd,0x1c3, 0x19a,0x387,0x339,0x259, +0x6eb,0x187,0x77d,0x271, 0x195,0x259,0x5ae,0x169, +}, +new int[] { // 11 +0xddb,0xf77,0xb6d,0x587, 0x2c3,0x03b,0xef5,0x1e3, 0xdbe, +}, +new int[] { // 12 +0x1aeb,0x0587,0x0a71,0x1dbd, 0x0559,0x0aa5,0x0a2e,0x0d43, 0x05aa,0x00f3,0x0696,0x03c6, +}, +new int[] { // 13 +0x35d7,0x2f6f,0x0aa3,0x1569, 0x150f,0x3d79,0x0dc3,0x309f/*?*/, +}, +new int[] { // 14 +0x75d7,0x5f7b,0x6a8f,0x29a3, +}, +new int[] { // 15 +0xddd7,0xaaaf,0x55c3,0xf77b, +}, +new int[] { // 16 +0x1baeb,0x1efaf,0x1d5bf,0x1cff3, +}, +new int[] { // 17 +0x36dd7,0x3bb7b,0x3df6f,0x2d547, +}, +new int[] { // 18 +0x75dd7,0x6f77b,0x7aaaf,0x5ddd3, +}, +new int[] { // 19 +0xdddd7,0xf777b,0xd5547,0xb6ddb, +}, +new int[] { // 20 +0x1baeeb,0x1efbaf,0x1aaabf,0x17bbeb, +}, +new int[] { // 21 +0x376dd7,0x3ddf7b,0x2d550f,0x0aaaa3, +}, +new int[] { // 22 +0x6eddd7,0x77777b,0x5dcd4f,0x5d76f9, +}, +new int[] { // 23 +0xdeddd7,0xb5b6eb,0x55552b,0x2aaac3, +}, +new int[] { // 24 +0x1dddbb7,0x1b76eeb,0x17bbf5f,0x1eeaa9f, +}, +new int[] { // 25 +}, +new int[] { // 26 +}, +new int[] { // 27 +}, +new int[] { // 28 +}, +new int[] { // 29 +}, +new int[] { // 30 +}, + }; + + unsafe void postprocess_coefs(FlacFrame frame, FlacSubframe sf, int ch) + { + if (eparams.development_mode < 0) + return; + if (sf.type != SubframeType.LPC || sf.order > 30) + return; + int orig_window = sf.window; + int orig_order = sf.order; + int orig_shift = sf.shift; + int orig_cbits = sf.cbits; + uint orig_size = sf.size; + var orig_coefs = stackalloc int[orig_order]; + for (int i = 0; i < orig_order; i++) orig_coefs[i] = sf.coefs[i]; + int orig_xx = -1; + int orig_seq = 0; + int maxxx = Math.Min(good_x[orig_order].Length, eparams.development_mode); + var pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, orig_order); + var pmin = Math.Min(m_settings.MinPartitionOrder, pmax); + ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS]; + + while (true) + { + var best_coefs = stackalloc int[orig_order]; + int best_shift = orig_shift; + int best_cbits = orig_cbits; + uint best_size = orig_size; + int best_xx = -1; + for (int xx = -1; xx < maxxx; xx++) + { + int x = xx; + if (xx < 0) + { + if (orig_xx < 0 || maxxx < 1/*3*/)// || (orig_xx >> orig_order) != 0) + continue; + x = orig_xx; + orig_seq++; + } + else + { + orig_seq = 0; + if (orig_order < good_x.Length && good_x[orig_order] != null) + x = good_x[orig_order][xx]; + } + + frame.current.type = SubframeType.LPC; + frame.current.order = orig_order; + frame.current.window = orig_window; + frame.current.shift = orig_shift; + frame.current.cbits = orig_cbits; + + if (((x >> orig_order) & 1) != 0) + { + frame.current.shift--; + frame.current.cbits--; + if (frame.current.shift < 0 || frame.current.cbits < 2) + continue; + } + + ulong csum = 0; + int qmax = (1 << (frame.current.cbits - 1)) - 1; + for (int i = 0; i < frame.current.order; i++) + { + int shift = (x >> orig_order) & 1; + int increment = (x == 1 << orig_order) ? 0 : (((x >> i) & 1) << 1) - 1; + frame.current.coefs[i] = (orig_coefs[i] + (increment << orig_seq)) >> shift; + if (frame.current.coefs[i] < -(qmax + 1)) frame.current.coefs[i] = -(qmax + 1); + if (frame.current.coefs[i] > qmax) frame.current.coefs[i] = qmax; + csum += (ulong)Math.Abs(frame.current.coefs[i]); + } + + fixed (int* coefs = frame.current.coefs) + { + if ((csum << frame.subframes[ch].obits) >= 1UL << 32) + lpc.encode_residual_long(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax); + else + lpc.encode_residual(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax); + } + + var cur_size = calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample); + frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits + 4 + 5 + frame.current.order * frame.current.cbits + 6 + (int)cur_size); + + if (frame.current.size < best_size) + { + //var dif = best_size - frame.current.size; + for (int i = 0; i < frame.current.order; i++) best_coefs[i] = frame.current.coefs[i]; + best_shift = frame.current.shift; + best_cbits = frame.current.cbits; + best_size = frame.current.size; + best_xx = x; + frame.ChooseBestSubframe(ch); + //if (dif > orig_order * 5) + // break; + } + + if (xx < 0 && best_size < orig_size) + break; + } + + if (best_size < orig_size) + { + //if (best_xx >= 0) best_x[order, best_xx]++; + //if (orig_size != 0x7FFFFFFF) + // System.Console.Write(string.Format(" {0}[{1:x}]", orig_size - best_size, best_xx)); + for (int i = 0; i < orig_order; i++) orig_coefs[i] = best_coefs[i]; + orig_shift = best_shift; + orig_cbits = best_cbits; + orig_size = best_size; + orig_xx = best_xx; + } + else + { + break; + } + } + + //if (orig_size != 0x7FFFFFFF) + // System.Console.WriteLine(); + + //if (frame_count % 0x400 == 0) + //{ + // for (int o = 0; o < best_x.GetLength(0); o++) + // { + // //for (int x = 0; x <= (1 << o); x++) + // // if (best_x[o, x] != 0) + // // System.Console.WriteLine(string.Format("{0:x2}\t{1:x4}\t{2}", o, x, best_x[o, x])); + // var s = new List>(); + // for (int x = 0; x < (1 << o); x++) + // if (best_x[o, x] != 0) + // s.Add(new KeyValuePair(x, best_x[o, x])); + // s.Sort((x, y) => y.Value.CompareTo(x.Value)); + // foreach (var x in s) + // System.Console.WriteLine(string.Format("{0:x2}\t{1:x4}\t{2}", o, x.Key, x.Value)); + // int i = 0; + // foreach (var x in s) + // { + // System.Console.Write(string.Format(o <= 8 ? "0x{0:x2}," : "0x{0:x3},", x.Key)); + // if ((++i) % 16 == 0) + // System.Console.WriteLine(); + // } + // System.Console.WriteLine(); + // } + //} + } + + public static void SetCoefs(int order, int[] coefs) + { + good_x[order] = new int[coefs.Length]; + for (int i = 0; i < coefs.Length; i++) + good_x[order][i] = coefs[i]; + } + + unsafe void encode_residual_lpc_sub(FlacFrame frame, float* lpcs, int iWindow, int order, int ch) + { + // select LPC precision based on block size + uint lpc_precision; + if (frame.blocksize <= 192) lpc_precision = 7U; + else if (frame.blocksize <= 384) lpc_precision = 8U; + else if (frame.blocksize <= 576) lpc_precision = 9U; + else if (frame.blocksize <= 1152) lpc_precision = 10U; + else if (frame.blocksize <= 2304) lpc_precision = 11U; + else if (frame.blocksize <= 4608) lpc_precision = 12U; + else if (frame.blocksize <= 8192) lpc_precision = 13U; + else if (frame.blocksize <= 16384) lpc_precision = 14U; + else lpc_precision = 15; + + for (int i_precision = m_settings.MinPrecisionSearch; i_precision <= m_settings.MaxPrecisionSearch && lpc_precision + i_precision < 16; i_precision++) + // check if we already calculated with this order, window and precision + if ((frame.subframes[ch].lpc_ctx[iWindow].done_lpcs[i_precision] & (1U << (order - 1))) == 0) + { + frame.subframes[ch].lpc_ctx[iWindow].done_lpcs[i_precision] |= (1U << (order - 1)); + + uint cbits = lpc_precision + (uint)i_precision; + + frame.current.type = SubframeType.LPC; + frame.current.order = order; + frame.current.window = iWindow; + frame.current.cbits = (int)cbits; + + int pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, frame.current.order); + int pmin = Math.Min(m_settings.MinPartitionOrder, pmax); + ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS]; + ulong csum = 0; + fixed (int* coefs = frame.current.coefs) + { + lpc.quantize_lpc_coefs(lpcs + (frame.current.order - 1) * lpc.MAX_LPC_ORDER, + frame.current.order, cbits, coefs, out frame.current.shift, 15, 0); + + if (frame.current.shift < 0 || frame.current.shift > 15) + throw new Exception("negative shift"); + + for (int i = frame.current.order; i > 0; i--) + csum += (ulong)Math.Abs(coefs[i - 1]); + + if ((csum << frame.subframes[ch].obits) >= 1UL << 32) + lpc.encode_residual_long(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax); + else + lpc.encode_residual(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax); + + } + uint best_size = calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample); + frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits + 4 + 5 + frame.current.order * (int)cbits + 6 + (int)best_size); + frame.ChooseBestSubframe(ch); + //if (frame.current.size >= frame.subframes[ch].best.size) + // postprocess_coefs(frame, frame.current, ch); + //else + //{ + // frame.ChooseBestSubframe(ch); + // postprocess_coefs(frame, frame.subframes[ch].best, ch); + //} + } + } + + unsafe void encode_residual_fixed_sub(FlacFrame frame, int order, int ch) + { + if ((frame.subframes[ch].done_fixed & (1U << order)) != 0) + return; // already calculated; + + frame.current.order = order; + frame.current.type = SubframeType.Fixed; + +#if XXX + int best_order = order; + if (frame.subframes[ch].done_fixed == 0) + { + best_order = encode_residual_fixed_estimate_best_order(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order); + if (best_order != order) + { + //frame.subframes[ch].done_fixed |= (1U << order); + order = best_order; + frame.current.order = order; + encode_residual_fixed(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order); + } + } + else +#endif + int pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, frame.current.order); + int pmin = Math.Min(m_settings.MinPartitionOrder, pmax); + ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS]; + encode_residual_fixed(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax); + + frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits) + 6 + + calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample); + + frame.subframes[ch].done_fixed |= (1U << order); + + frame.ChooseBestSubframe(ch); + } + + unsafe void fixed_compute_best_predictor(int* data, uint data_len, ulong* errors)//, float* residual_bits_per_sample) + { + long last_error_0 = data[-1]; + long last_error_1 = data[-1] - data[-2]; + long last_error_2 = last_error_1 - (data[-2] - data[-3]); + long last_error_3 = last_error_2 - (data[-2] - 2 * data[-3] + data[-4]); + ulong total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0; + +#if VARIANT1 + long error, save; + int* finish = data + data_len; + while (data < finish) + { + error = *(data++); total_error_0 += (ulong)((error << 1) ^ (error >> 63)); save = error; + error -= last_error_0; total_error_1 += (ulong)((error << 1) ^ (error >> 63)); last_error_0 = save; save = error; + error -= last_error_1; total_error_2 += (ulong)((error << 1) ^ (error >> 63)); last_error_1 = save; save = error; + error -= last_error_2; total_error_3 += (ulong)((error << 1) ^ (error >> 63)); last_error_2 = save; save = error; + error -= last_error_3; total_error_4 += (ulong)((error << 1) ^ (error >> 63)); last_error_3 = save; + } +#else + int* finish = data + data_len; + while (data < finish) + { + long next_error_0 = *(data++); + long next_error_1 = next_error_0 - last_error_0; + long next_error_2 = next_error_1 - last_error_1; + long next_error_3 = next_error_2 - last_error_2; + long next_error_4 = next_error_3 - last_error_3; + + last_error_0 = next_error_0; + last_error_1 = next_error_1; + last_error_2 = next_error_2; + last_error_3 = next_error_3; + + total_error_0 += (ulong)((last_error_0 << 1) ^ (last_error_0 >> 63)); + total_error_1 += (ulong)((last_error_1 << 1) ^ (last_error_1 >> 63)); + total_error_2 += (ulong)((last_error_2 << 1) ^ (last_error_2 >> 63)); + total_error_3 += (ulong)((last_error_3 << 1) ^ (last_error_3 >> 63)); + total_error_4 += (ulong)((next_error_4 << 1) ^ (next_error_4 >> 63)); + } +#endif + + errors[0] = total_error_0; + errors[1] = total_error_1; + errors[2] = total_error_2; + errors[3] = total_error_3; + errors[4] = total_error_4; + + //residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0); + //residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (FLAC__double)total_error_1 / (FLAC__double)data_len) / M_LN2 : 0.0); + //residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (FLAC__double)total_error_2 / (FLAC__double)data_len) / M_LN2 : 0.0); + //residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (FLAC__double)total_error_3 / (FLAC__double)data_len) / M_LN2 : 0.0); + //residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (FLAC__double)total_error_4 / (FLAC__double)data_len) / M_LN2 : 0.0); + } + + unsafe int fixed_compute_best_predictor_order(ulong* error) + { + int order; + if ((error[0] < error[1]) & (error[0] < error[2]) & (error[0] < error[3]) & (error[0] < error[4])) + order = 0; + else if ((error[1] < error[2]) & (error[1] < error[3]) & (error[1] < error[4])) + order = 1; + else if ((error[2] < error[3]) & (error[2] < error[4])) + order = 2; + else if (error[3] < error[4]) + order = 3; + else + order = 4; + return order; + } + + unsafe void encode_residual(FlacFrame frame, int ch, PredictionType predict, OrderMethod omethod, int pass, int windows_mask) + { + int* smp = frame.subframes[ch].samples; + int i, n = frame.blocksize; + // save best.window, because we can overwrite it later with fixed frame + + // CONSTANT + for (i = 1; i < n; i++) + { + if (smp[i] != smp[0]) break; + } + if (i == n) + { + frame.subframes[ch].best.type = SubframeType.Constant; + frame.subframes[ch].best.residual[0] = smp[0]; + frame.subframes[ch].best.size = (uint)frame.subframes[ch].obits; + return; + } + + // VERBATIM + frame.current.type = SubframeType.Verbatim; + frame.current.size = (uint)(frame.subframes[ch].obits * frame.blocksize); + frame.ChooseBestSubframe(ch); + + if (n < 5 || predict == PredictionType.None) + return; + + // LPC + if (n > m_settings.MaxLPCOrder && + (predict == PredictionType.Levinson || + predict == PredictionType.Search) + //predict == PredictionType.Search || + //(pass == 2 && frame.subframes[ch].best.type == SubframeType.LPC)) + ) + { + float* lpcs = stackalloc float[lpc.MAX_LPC_ORDER * lpc.MAX_LPC_ORDER]; + int min_order = m_settings.MinLPCOrder; + int max_order = m_settings.MaxLPCOrder; + + for (int iWindow = 0; iWindow < _windowcount; iWindow++) + { + if ((windows_mask & (1 << iWindow)) == 0) + continue; + + LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[iWindow]; + fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0]) + lpc_ctx.GetReflection( + frame.subframes[ch].sf, max_order, frame.blocksize, + smp, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections); + lpc_ctx.ComputeLPC(lpcs); + + //int frameSize = n; + //float* F = stackalloc float[frameSize]; + //float* B = stackalloc float[frameSize]; + //float* PE = stackalloc float[max_order + 1]; + //float* arp = stackalloc float[max_order]; + //float* rc = stackalloc float[max_order]; + + //for (int j = 0; j < frameSize; j++) + // F[j] = B[j] = smp[j]; + + //for (int K = 1; K <= max_order; K++) + //{ + // // BURG: + // float denominator = 0.0f; + // //float denominator = F[K - 1] * F[K - 1] + B[frameSize - K] * B[frameSize - K]; + // for (int j = 0; j < frameSize - K; j++) + // denominator += F[j + K] * F[j + K] + B[j] * B[j]; + // denominator /= 2; + + // // Estimate error + // PE[K - 1] = denominator / (frameSize - K); + + // float reflectionCoeff = 0.0f; + // for (int j = 0; j < frameSize - K; j++) + // reflectionCoeff += F[j + K] * B[j]; + // reflectionCoeff /= denominator; + // rc[K - 1] = arp[K - 1] = reflectionCoeff; + + // // Levinson-Durbin + // for (int j = 0; j < (K - 1) >> 1; j++) + // { + // float arptmp = arp[j]; + // arp[j] -= reflectionCoeff * arp[K - 2 - j]; + // arp[K - 2 - j] -= reflectionCoeff * arptmp; + // } + // if (((K - 1) & 1) != 0) + // arp[(K - 1) >> 1] -= reflectionCoeff * arp[(K - 1) >> 1]; + + // for (int j = 0; j < frameSize - K; j++) + // { + // float f = F[j + K]; + // float b = B[j]; + // F[j + K] = f - reflectionCoeff * b; + // B[j] = b - reflectionCoeff * f; + // } + + // for (int j = 0; j < K; j++) + // lpcs[(K - 1) * lpc.MAX_LPC_ORDER + j] = (float)arp[j]; + //} + + switch (omethod) + { + case OrderMethod.Akaike: + //lpc_ctx.SortOrdersAkaike(frame.blocksize, m_settings.EstimationDepth, max_order, 7.1, 0.0); + lpc_ctx.SortOrdersAkaike(frame.blocksize, m_settings.EstimationDepth, min_order, max_order, 4.5, 0); + break; + default: + throw new Exception("unknown order method"); + } + + for (i = 0; i < m_settings.EstimationDepth && i < max_order; i++) + encode_residual_lpc_sub(frame, lpcs, iWindow, lpc_ctx.best_orders[i], ch); + } + + postprocess_coefs(frame, frame.subframes[ch].best, ch); + } + + // FIXED + if (predict == PredictionType.Fixed || + (predict == PredictionType.Search && pass != 1) || + //predict == PredictionType.Search || + //(pass == 2 && frame.subframes[ch].best.type == SubframeType.Fixed) || + (n > m_settings.MaxFixedOrder && n <= m_settings.MaxLPCOrder)) + { + int max_fixed_order = Math.Min(m_settings.MaxFixedOrder, 4); + int min_fixed_order = Math.Min(m_settings.MinFixedOrder, max_fixed_order); + + if (min_fixed_order == 0 && max_fixed_order == 4) + { + fixed (ulong* fixed_errors = frame.subframes[ch].best_fixed) + { + if ((frame.subframes[ch].done_fixed & (1U << 5)) == 0) + { + fixed_compute_best_predictor(smp + 4, (uint)n - 4, fixed_errors); + frame.subframes[ch].done_fixed |= (1U << 5); + } + i = fixed_compute_best_predictor_order(fixed_errors); + encode_residual_fixed_sub(frame, i, ch); + } + } + else + { + for (i = max_fixed_order; i >= min_fixed_order; i--) + encode_residual_fixed_sub(frame, i, ch); + } + } + + } + + unsafe void output_frame_header(FlacFrame frame, BitWriter bitwriter) + { + bitwriter.writebits(15, 0x7FFC); + bitwriter.writebits(1, eparams.variable_block_size > 0 ? 1 : 0); + bitwriter.writebits(4, frame.bs_code0); + bitwriter.writebits(4, sr_code0); + if (frame.ch_mode == ChannelMode.NotStereo) + bitwriter.writebits(4, ch_code); + else + bitwriter.writebits(4, (int) frame.ch_mode); + bitwriter.writebits(3, bps_code); + bitwriter.writebits(1, 0); + bitwriter.write_utf8(frame_count); + + // custom block size + if (frame.bs_code1 >= 0) + { + if (frame.bs_code1 < 256) + bitwriter.writebits(8, frame.bs_code1); + else + bitwriter.writebits(16, frame.bs_code1); + } + + // custom sample rate + if (sr_code1 > 0) + { + if (sr_code1 < 256) + bitwriter.writebits(8, sr_code1); + else + bitwriter.writebits(16, sr_code1); + } + + // CRC-8 of frame header + bitwriter.flush(); + byte crc = crc8.ComputeChecksum(frame_buffer, 0, bitwriter.Length); + bitwriter.writebits(8, crc); + } + + unsafe void output_residual(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub) + { + // rice-encoded block + bitwriter.writebits(2, sub.best.rc.coding_method); + + // partition order + int porder = sub.best.rc.porder; + int psize = frame.blocksize >> porder; + //assert(porder >= 0); + bitwriter.writebits(4, porder); + int res_cnt = psize - sub.best.order; + + int rice_len = 4 + sub.best.rc.coding_method; + // residual + int j = sub.best.order; + fixed (byte* fixbuf = &frame_buffer[0]) + for (int p = 0; p < (1 << porder); p++) + { + int k = sub.best.rc.rparams[p]; + bitwriter.writebits(rice_len, k); + if (p == 1) res_cnt = psize; + int cnt = Math.Min(res_cnt, frame.blocksize - j); + bitwriter.write_rice_block_signed(fixbuf, k, sub.best.residual + j, cnt); + j += cnt; + } + } + + unsafe void + output_subframe_constant(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub) + { + bitwriter.writebits_signed(sub.obits, sub.best.residual[0]); + } + + unsafe void + output_subframe_verbatim(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub) + { + int n = frame.blocksize; + for (int i = 0; i < n; i++) + bitwriter.writebits_signed(sub.obits, sub.samples[i]); + // Don't use residual here, because we don't copy samples to residual for verbatim frames. + } + + unsafe void + output_subframe_fixed(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub) + { + // warm-up samples + for (int i = 0; i < sub.best.order; i++) + bitwriter.writebits_signed(sub.obits, sub.best.residual[i]); + + // residual + output_residual(frame, bitwriter, sub); + } + + unsafe void + output_subframe_lpc(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub) + { + // warm-up samples + for (int i = 0; i < sub.best.order; i++) + bitwriter.writebits_signed(sub.obits, sub.best.residual[i]); + + // LPC coefficients + int cbits = 1; + for (int i = 0; i < sub.best.order; i++) + while (cbits < 16 && sub.best.coefs[i] != (sub.best.coefs[i] << (32 - cbits)) >> (32 - cbits)) + cbits++; + bitwriter.writebits(4, cbits - 1); + bitwriter.writebits_signed(5, sub.best.shift); + for (int i = 0; i < sub.best.order; i++) + bitwriter.writebits_signed(cbits, sub.best.coefs[i]); + + // residual + output_residual(frame, bitwriter, sub); + } + + unsafe void output_subframes(FlacFrame frame, BitWriter bitwriter) + { + for (int ch = 0; ch < channels; ch++) + { + FlacSubframeInfo sub = frame.subframes[ch]; + // subframe header + int type_code = (int) sub.best.type; + if (sub.best.type == SubframeType.Fixed) + type_code |= sub.best.order; + if (sub.best.type == SubframeType.LPC) + type_code |= sub.best.order - 1; + bitwriter.writebits(1, 0); + bitwriter.writebits(6, type_code); + bitwriter.writebits(1, sub.wbits != 0 ? 1 : 0); + if (sub.wbits > 0) + bitwriter.writebits((int)sub.wbits, 1); + + // subframe + switch (sub.best.type) + { + case SubframeType.Constant: + output_subframe_constant(frame, bitwriter, sub); + break; + case SubframeType.Verbatim: + output_subframe_verbatim(frame, bitwriter, sub); + break; + case SubframeType.Fixed: + output_subframe_fixed(frame, bitwriter, sub); + break; + case SubframeType.LPC: + output_subframe_lpc(frame, bitwriter, sub); + break; + } + } + } + + void output_frame_footer(BitWriter bitwriter) + { + bitwriter.flush(); + ushort crc = bitwriter.get_crc16(); + bitwriter.writebits(16, crc); + bitwriter.flush(); + } + + unsafe void encode_residual_pass1(FlacFrame frame, int ch, int windows_mask) + { + int max_prediction_order = m_settings.MaxLPCOrder; + //int max_fixed_order = m_settings.MaxFixedOrder; + //int min_fixed_order = m_settings.MinFixedOrder; + int lpc_min_precision_search = m_settings.MinPrecisionSearch; + int lpc_max_precision_search = m_settings.MaxPrecisionSearch; + int max_partition_order = m_settings.MaxPartitionOrder; + int estimation_depth = m_settings.EstimationDepth; + var development_mode = eparams.development_mode; + //m_settings.MinFixedOrder = 2; + //m_settings.MaxFixedOrder = 2; + m_settings.MinPrecisionSearch = m_settings.MaxPrecisionSearch; + m_settings.MaxLPCOrder = Math.Min(m_settings.MaxLPCOrder, Math.Max(m_settings.MinLPCOrder, 8)); + m_settings.EstimationDepth = 1; + eparams.development_mode = Math.Min(eparams.development_mode, -1); + encode_residual(frame, ch, m_settings.PredictionType, OrderMethod.Akaike, 1, windows_mask); + //m_settings.MinFixedOrder = min_fixed_order; + //m_settings.MaxFixedOrder = max_fixed_order; + m_settings.MaxLPCOrder = max_prediction_order; + m_settings.MinPrecisionSearch = lpc_min_precision_search; + m_settings.MaxPrecisionSearch = lpc_max_precision_search; + m_settings.MaxPartitionOrder = max_partition_order; + m_settings.EstimationDepth = estimation_depth; + eparams.development_mode = development_mode; + } + + unsafe void encode_residual_pass2(FlacFrame frame, int ch) + { + encode_residual(frame, ch, m_settings.PredictionType, eparams.order_method, 2, estimate_best_windows(frame, ch)); + } + + unsafe int estimate_best_windows_akaike(FlacFrame frame, int ch, int count, bool onePerType) + { + int* windows_present = stackalloc int[_windowcount]; + for (int i = 0; i < _windowcount; i++) + windows_present[i] = 0; + if (onePerType) + { + for (int i = 0; i < _windowcount; i++) + for (int j = 0; j < _windowcount; j++) + if (windowType[j] == windowType[i]) + windows_present[j]++; + } + + float* err = stackalloc float[lpc.MAX_LPC_ORDER]; + for (int i = 0; i < _windowcount; i++) + { + LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[i]; + if (onePerType && windows_present[i] <= count) + { + err[i] = 0; + continue; + } + int estimate_order = 4; + fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, i, 0]) + lpc_ctx.GetReflection( + frame.subframes[ch].sf, estimate_order, frame.blocksize, + frame.subframes[ch].samples, frame.window_buffer + i * FlakeConstants.MAX_BLOCKSIZE * 2, sections); + lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0); + //err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0)); + //err[i] = (float)((frame.blocksize * lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1] / windowScale[i]) + lpc_ctx.best_orders[0] * 4.5); + //err[i] = (float)((frame.blocksize * lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1] / windowScale[i]) + lpc_ctx.best_orders[0] * frame.subframes[ch].obits); + + // realistic + //err[i] = (float)(frame.blocksize * Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1]) / Math.Log(2) / 2.5 + //- windowScale[i] / 2 + lpc_ctx.best_orders[0] * frame.subframes[ch].obits / 2); + + //err[i] = (float)(frame.blocksize * Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1]) / Math.Log(2) / 2.5 + //- frame.blocksize * Math.Log(lpc_ctx.autocorr_values[0]) / 2.1 + //+ Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5 / 2.5 / Math.Log(2)); + + // Akaike + //err[i] = (float)(frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5); + + //err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) - frame.blocksize * (frame.subframes[ch].obits + Math.Log(windowScale[i] / frame.blocksize) / 2)); + + // tested good + err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) - frame.blocksize * Math.Log(lpc_ctx.autocorr_values[0]) / 2); + } + int* best_windows = stackalloc int[lpc.MAX_LPC_ORDER]; + for (int i = 0; i < _windowcount; i++) + best_windows[i] = i; + for (int i = 0; i < _windowcount; i++) + { + for (int j = i + 1; j < _windowcount; j++) + { + if (err[best_windows[i]] > err[best_windows[j]]) + { + int tmp = best_windows[j]; + best_windows[j] = best_windows[i]; + best_windows[i] = tmp; + } + } + } + int window_mask = 0; + if (onePerType) + { + for (int i = 0; i < _windowcount; i++) + windows_present[i] = count; + for (int i = 0; i < _windowcount; i++) + { + int w = best_windows[i]; + if (windows_present[w] > 0) + { + for (int j = 0; j < _windowcount; j++) + if (windowType[j] == windowType[w]) + windows_present[j]--; + window_mask |= 1 << w; + } + } + } + else + { + for (int i = 0; i < _windowcount && i < count; i++) + window_mask |= 1 << best_windows[i]; + } + return window_mask; + } + + unsafe int estimate_best_windows(FlacFrame frame, int ch) + { + if (_windowcount == 1 || m_settings.PredictionType == PredictionType.Fixed) + return 1; + switch (m_settings.WindowMethod) + { + case WindowMethod.Estimate: + return estimate_best_windows_akaike(frame, ch, 1, false); + case WindowMethod.Estimate2: + return estimate_best_windows_akaike(frame, ch, 2, false); + case WindowMethod.Estimate3: + return estimate_best_windows_akaike(frame, ch, 3, false); + case WindowMethod.EstimateN: + return estimate_best_windows_akaike(frame, ch, 1, true); + case WindowMethod.Evaluate2: + encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 2, false)); + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.Evaluate3: + encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 3, false)); + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.EvaluateN: + encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 1, true)); +#if XXX + if (frame.subframes[ch].best.type == SubframeType.LPC && frame.subframes[ch].best.order <= 4) + { + LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[frame.subframes[ch].best.window]; + double err = lpc_ctx.prediction_error[frame.subframes[ch].best.order - 1] / lpc_ctx.autocorr_values[0]; + double est = frame.blocksize * (frame.subframes[ch].obits * (1 - err)); + double est1 = frame.blocksize * (frame.subframes[ch].obits * (err)); + if (est < 0 || est1 < 0) return -1; + } +#endif + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.Evaluate2N: + encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 2, true)); + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.Evaluate3N: + encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 3, true)); + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.Evaluate: + encode_residual_pass1(frame, ch, -1); + return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0; + case WindowMethod.Search: + return -1; + } + return -1; + } + + unsafe void estimate_frame(FlacFrame frame, bool do_midside) + { + int subframes = do_midside ? channels * 2 : channels; + + switch (m_settings.StereoMethod) + { + case StereoMethod.Estimate: + for (int ch = 0; ch < subframes; ch++) + { + LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[0]; + int estimate_order = 4; + int iWindow = 0; + fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0]) + lpc_ctx.GetReflection( + frame.subframes[ch].sf, estimate_order, frame.blocksize, + frame.subframes[ch].samples, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections); + lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0); + frame.subframes[ch].best.size + = (uint)Math.Max(0, frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5 + //= (uint)Math.Max(0, lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) + //* 2.0 / Math.Log(windowScale[0] / frame.blocksize) + + 7.1 * frame.subframes[ch].obits * m_settings.MaxLPCOrder); + } + break; + case StereoMethod.EstimateFixed: + for (int ch = 0; ch < subframes; ch++) + { + fixed (ulong* fixed_errors = frame.subframes[ch].best_fixed) + { + if ((frame.subframes[ch].done_fixed & (1U << 5)) == 0) + { + fixed_compute_best_predictor(frame.subframes[ch].samples + 4, (uint)frame.blocksize - 4, fixed_errors); + frame.subframes[ch].done_fixed |= (1U << 5); + } + int best_order = fixed_compute_best_predictor_order(fixed_errors); + //residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0); + frame.subframes[ch].best.size = (uint)fixed_errors[best_order]; + } + } + break; + case StereoMethod.EstimateX: + for (int ch = 0; ch < subframes; ch++) + { + for (int iWindow = 0; iWindow < _windowcount; iWindow++) + { + LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[iWindow]; + int estimate_order = 4; + fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0]) + lpc_ctx.GetReflection( + frame.subframes[ch].sf, estimate_order, frame.blocksize, + frame.subframes[ch].samples, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections); + lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0); + uint estimate + = (uint)Math.Max(0, frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5 + //= (uint)Math.Max(0, lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) + //* 2.0 / Math.Log(windowScale[0] / frame.blocksize) + + 7.1 * frame.subframes[ch].obits * m_settings.MaxLPCOrder); + if (iWindow == 0 || frame.subframes[ch].best.size > estimate) + frame.subframes[ch].best.size = estimate; + } + } + break; + case StereoMethod.Evaluate: + for (int ch = 0; ch < subframes; ch++) + encode_residual_pass1(frame, ch, 1); + break; + case StereoMethod.EvaluateX: + for (int ch = 0; ch < subframes; ch++) + encode_residual_pass1(frame, ch, + estimate_best_windows_akaike(frame, ch, 1, false)); + break; + case StereoMethod.Search: + for (int ch = 0; ch < subframes; ch++) + encode_residual_pass2(frame, ch); + break; + } + } + + unsafe uint measure_frame_size(FlacFrame frame, bool do_midside) + { + // crude estimation of header/footer size + uint total = (uint)(32 + ((BitReader.log2i(frame_count) + 4) / 5) * 8 + (eparams.variable_block_size != 0 ? 16 : 0) + 16); + + if (do_midside) + { + uint bitsBest = AudioSamples.UINT32_MAX; + ChannelMode modeBest = ChannelMode.LeftRight; + + if (bitsBest > frame.subframes[2].best.size + frame.subframes[3].best.size) + { + bitsBest = frame.subframes[2].best.size + frame.subframes[3].best.size; + modeBest = ChannelMode.MidSide; + } + if (bitsBest > frame.subframes[3].best.size + frame.subframes[1].best.size) + { + bitsBest = frame.subframes[3].best.size + frame.subframes[1].best.size; + modeBest = ChannelMode.RightSide; + } + if (bitsBest > frame.subframes[3].best.size + frame.subframes[0].best.size) + { + bitsBest = frame.subframes[3].best.size + frame.subframes[0].best.size; + modeBest = ChannelMode.LeftSide; + } + if (bitsBest > frame.subframes[0].best.size + frame.subframes[1].best.size) + { + bitsBest = frame.subframes[0].best.size + frame.subframes[1].best.size; + modeBest = ChannelMode.LeftRight; + } + frame.ch_mode = modeBest; + return total + bitsBest; + } + + for (int ch = 0; ch < channels; ch++) + total += frame.subframes[ch].best.size; + return total; + } + + unsafe void encode_estimated_frame(FlacFrame frame) + { + switch (m_settings.StereoMethod) + { + case StereoMethod.Estimate: + case StereoMethod.EstimateX: + case StereoMethod.EstimateFixed: + for (int ch = 0; ch < channels; ch++) + { + frame.subframes[ch].best.size = AudioSamples.UINT32_MAX; + encode_residual_pass2(frame, ch); + } + break; + case StereoMethod.Evaluate: + case StereoMethod.EvaluateX: + for (int ch = 0; ch < channels; ch++) + encode_residual_pass2(frame, ch); + break; + case StereoMethod.Search: + break; + } + } + + unsafe delegate void window_function(float* window, int size); + + unsafe void calculate_window(float* window, window_function func, WindowFunction flag) + { + if ((m_settings.WindowFunctions & flag) == 0 || _windowcount == lpc.MAX_LPC_WINDOWS) + return; + int sz = _windowsize; + float* pos1 = window + _windowcount * FlakeConstants.MAX_BLOCKSIZE * 2; + float* pos = pos1; + int nSeg = 0; + do + { + windowSections[nSeg, _windowcount, 0].setData(0, sz); + for (int j = 1; j < lpc.MAX_LPC_SECTIONS; j++) + windowSections[nSeg, _windowcount, j].setZero(sz, sz); + + fixed (LpcWindowSection* sections = &windowSections[nSeg, _windowcount, 0]) + func(pos, sz); + if ((sz & 1) != 0) + break; + nSeg++; + pos += sz; + sz >>= 1; + } while (sz >= 32); + double scale = 0.0; + for (int i = 0; i < _windowsize; i++) + scale += pos1[i] * pos1[i]; + windowScale[_windowcount] = scale; + windowType[_windowcount] = flag; + _windowcount++; + } + + class PunchoutTukeyVariant + { + public PunchoutTukeyVariant( + WindowFunction _type, + int _parts, double _overlap, double _p) + { + parts = _parts; + type = _type; + overlap = _overlap; + p = _p; + } + public WindowFunction type; + public int parts; + public double overlap; + public double p; + }; + + unsafe int encode_frame(out int size) + { + fixed (int* s = samplesBuffer, r = residualBuffer) + fixed (float* window = windowBuffer) + { + frame.InitSize(m_blockSize, eparams.variable_block_size != 0); + + if (frame.blocksize != _windowsize && frame.blocksize > 4 && m_settings.PredictionType != PredictionType.Fixed) + { + _windowsize = frame.blocksize; + _windowcount = 0; + calculate_window(window, lpc.window_welch, WindowFunction.Welch); + calculate_window(window, lpc.window_flattop, WindowFunction.Flattop); + calculate_window(window, lpc.window_hann, WindowFunction.Hann); + calculate_window(window, lpc.window_bartlett, WindowFunction.Bartlett); + var tukeys = new PunchoutTukeyVariant[] + { + new PunchoutTukeyVariant(WindowFunction.Tukey4, 4, 0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey4A, 4, 0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey4B, 4, 0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey4X, 4, m_settings.TukeyOverlap, m_settings.TukeyP), + new PunchoutTukeyVariant(WindowFunction.Tukey3, 3, 1.0/3, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey3A, 3, 1.0/3, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey3B, 3, 1.0/3, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey3X, 3, m_settings.TukeyOverlap, m_settings.TukeyP), + new PunchoutTukeyVariant(WindowFunction.Tukey2, 2, 0.25, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey2A, 2, 0.25, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey2B, 2, 0.25, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey2X, 2, m_settings.TukeyOverlap, m_settings.TukeyP), + new PunchoutTukeyVariant(WindowFunction.Tukey, 1, 0.0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey1A, 1, 0.0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey1B, 1, 0.0, 0.03), + new PunchoutTukeyVariant(WindowFunction.Tukey1X, 1, m_settings.TukeyOverlap, m_settings.TukeyP), + }; + + foreach (var tukey in tukeys) + { + if (tukey.parts == 0 || (m_settings.WindowFunctions & tukey.type) == 0) continue; + if (tukey.parts == 1) + { + calculate_window(window, (w, wsz) => + { + lpc.window_tukey(w, wsz, tukey.p); + }, tukey.type); + continue; + } + double overlap = tukey.overlap; + double overlap_units = overlap / (1.0 - overlap); + for (int m = 0; m < tukey.parts; m++) + calculate_window(window, (w, wsz) => + { + lpc.window_punchout_tukey(w, wsz, tukey.p, tukey.p, + m / (tukey.parts + overlap_units), + (m + 1 + overlap_units) / (tukey.parts + overlap_units)); + }, tukey.type); + } + + if (_windowcount == 0) + throw new Exception("invalid windowfunction"); + int nSeg = 0; + int sz = _windowsize; + float* window_segment = window; + do + { + fixed (LpcWindowSection* sections = &windowSections[nSeg, 0, 0]) + LpcWindowSection.Detect(_windowcount, window_segment, FlakeConstants.MAX_BLOCKSIZE * 2, sz, Settings.PCM.BitsPerSample, sections); + if ((sz & 1) != 0) + break; + window_segment += sz; + nSeg++; + sz >>= 1; + } while (sz >= 32); +#if NONONO + using (TextWriter tx = File.CreateText(@"H:\ubuntu\flac\w.txt")) + { +#if !NONONO + int totaltotal = 0; + for (int i = 0; i < _windowcount; i++) + { + int total = 0; + for (int sec = 0; sec < lpc.MAX_LPC_SECTIONS; sec++) + if (windowSections[0, i, sec].m_type != LpcWindowSection.SectionType.Zero || windowSections[0, i, sec].m_start != windowSections[0, i, sec].m_end) + { + tx.WriteLine("{0}\t{1}\t{2}\t{3}", windowSections[0, i, sec].m_start, windowSections[0, i, sec].m_end, windowSections[0, i, sec].m_type, windowSections[0, i, sec].m_id); + if (windowSections[0, i, sec].m_type != LpcWindowSection.SectionType.One) + total += windowSections[0, i, sec].m_end - windowSections[0, i, sec].m_start; + } + totaltotal += total; + tx.WriteLine("{0} total window data", total); + } + tx.WriteLine("{0} grand total window data", totaltotal); +#endif + for (int x = 0; x < frame.blocksize; x++) + { + tx.Write("{0}", x); + for (int i = 0; i < _windowcount; i++) + tx.Write("\t{0}", window[i * FlakeConstants.MAX_BLOCKSIZE * 2 + x]); + tx.WriteLine(); + } + } +#endif + } + + if (channels != 2 || frame.blocksize <= 32 || m_settings.StereoMethod == StereoMethod.Independent) + { + frame.window_buffer = window; + frame.nSeg = 0; + frame.current.residual = r + channels * FlakeConstants.MAX_BLOCKSIZE; + frame.ch_mode = channels != 2 ? ChannelMode.NotStereo : ChannelMode.LeftRight; + for (int ch = 0; ch < channels; ch++) + frame.subframes[ch].Init(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, + Settings.PCM.BitsPerSample, get_wasted_bits(s + ch * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize)); + + for (int ch = 0; ch < channels; ch++) + encode_residual_pass2(frame, ch); + } + else + { + //channel_decorrelation(s, s + FlakeConstants.MAX_BLOCKSIZE, s + 2 * FlakeConstants.MAX_BLOCKSIZE, s + 3 * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize); + frame.window_buffer = window; + frame.nSeg = 0; + frame.current.residual = r + 4 * FlakeConstants.MAX_BLOCKSIZE; + for (int ch = 0; ch < 4; ch++) + frame.subframes[ch].Init(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, + Settings.PCM.BitsPerSample + (ch == 3 ? 1 : 0), get_wasted_bits(s + ch * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize)); + + //for (int ch = 0; ch < 4; ch++) + // for (int iWindow = 0; iWindow < _windowcount; iWindow++) + // frame.subframes[ch].lpc_ctx[iWindow].GetReflection(32, frame.subframes[ch].samples, frame.blocksize, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2); + + estimate_frame(frame, true); + uint fs = measure_frame_size(frame, true); + + if (0 != eparams.variable_block_size) + { + FlacFrame frame2 = new FlacFrame(channels * 2); + FlacFrame frame3 = new FlacFrame(channels * 2); + int tumbler = 1; + while ((frame.blocksize & 1) == 0 && frame.blocksize >= 1024) + { + frame2.InitSize(frame.blocksize / 2, true); + frame2.window_buffer = frame.window_buffer + frame.blocksize; + frame2.nSeg = frame.nSeg + 1; + frame2.current.residual = r + tumbler * 5 * FlakeConstants.MAX_BLOCKSIZE; + for (int ch = 0; ch < 4; ch++) + frame2.subframes[ch].Init(frame.subframes[ch].samples, frame2.current.residual + (ch + 1) * frame2.blocksize, + frame.subframes[ch].obits + frame.subframes[ch].wbits, frame.subframes[ch].wbits); + estimate_frame(frame2, true); + //measure_frame_size(frame2, true); + //frame2.ChooseSubframes(); + //encode_estimated_frame(frame2); + //uint fs2 = measure_frame_size(frame2, false); + uint fs2 = measure_frame_size(frame2, true); + uint fs3 = fs2; + if (eparams.variable_block_size == 2 || eparams.variable_block_size == 4) + { + frame3.InitSize(frame2.blocksize, true); + frame3.window_buffer = frame2.window_buffer; + frame3.nSeg = frame2.nSeg; + frame3.current.residual = frame2.current.residual + 5 * frame2.blocksize; + for (int ch = 0; ch < 4; ch++) + frame3.subframes[ch].Init(frame2.subframes[ch].samples + frame2.blocksize, frame3.current.residual + (ch + 1) * frame3.blocksize, + frame.subframes[ch].obits + frame.subframes[ch].wbits, frame.subframes[ch].wbits); + estimate_frame(frame3, true); + fs3 = measure_frame_size(frame3, true); + } + if (fs2 + fs3 > fs) + break; + FlacFrame tmp = frame; + frame = frame2; + frame2 = tmp; + fs = fs2; + if (eparams.variable_block_size <= 2) + break; + tumbler = 1 - tumbler; + } + } + + frame.ChooseSubframes(); + encode_estimated_frame(frame); + } + + BitWriter bitwriter = new BitWriter(frame_buffer, 0, max_frame_size); + + output_frame_header(frame, bitwriter); + output_subframes(frame, bitwriter); + output_frame_footer(bitwriter); + + if (bitwriter.Length >= max_frame_size) + throw new Exception("buffer overflow"); + + if (frame_buffer != null) + { + if (eparams.variable_block_size > 0) + frame_count += frame.blocksize; + else + frame_count++; + } + size = frame.blocksize; + return bitwriter.Length; + } + } + + unsafe int output_frame() + { + if (verify != null) + { + fixed (int* s = verifyBuffer, r = samplesBuffer) + for (int ch = 0; ch < channels; ch++) + AudioSamples.MemCpy(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, m_blockSize); + } + + int fs, bs; + //if (0 != eparams.variable_block_size && 0 == (m_blockSize & 7) && m_blockSize >= 128) + // fs = encode_frame_vbs(); + //else + fs = encode_frame(out bs); + + if (seek_table != null && _IO.CanSeek) + { + for (int sp = 0; sp < seek_table.Length; sp++) + { + if (seek_table[sp].framesize != 0) + continue; + if (seek_table[sp].number > _position + bs) + break; + if (seek_table[sp].number >= _position) + { + seek_table[sp].number = _position; + seek_table[sp].offset = _IO.Position - first_frame_offset; + seek_table[sp].framesize = bs; + } + } + } + + _position += bs; + _IO.Write(frame_buffer, 0, fs); + _totalSize += fs; + + if (verify != null) + try + { + int decoded = verify.DecodeFrame(frame_buffer, 0, fs); + if (decoded != fs || verify.Remaining != bs) + throw new Exception(Properties.Resources.ExceptionValidationFailed); + fixed (int* s = verifyBuffer, r = verify.Samples) + { + for (int ch = 0; ch < channels; ch++) + if (AudioSamples.MemCmp(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, bs)) + throw new Exception(Properties.Resources.ExceptionValidationFailed); + } + } + catch (Exception ex) + { + //if (channels == 2) + //{ + // var sw = new WAVWriter(string.Format("verify_{0}.wav", this.frame_count), new WAVWriterSettings(this.Settings.PCM)); + // sw.FinalSampleCount = this.frame.blocksize; + // var ab = new AudioBuffer(this.Settings.PCM, this.frame.blocksize); + // ab.Prepare(this.frame.blocksize); + // fixed (int* abs = ab.Samples, s = verifyBuffer) + // AudioSamples.Interlace(abs, s, s + FlakeConstants.MAX_BLOCKSIZE, this.frame.blocksize); + // sw.Write(ab); + // sw.Close(); + //} else + throw ex; + } + + if (bs < m_blockSize) + { + for (int ch = 0; ch < (channels == 2 ? 4 : channels); ch++) + Buffer.BlockCopy(samplesBuffer, (bs + ch * FlakeConstants.MAX_BLOCKSIZE) * sizeof(int), samplesBuffer, ch * FlakeConstants.MAX_BLOCKSIZE * sizeof(int), (m_blockSize - bs) * sizeof(int)); + //fixed (int* s = samplesBuffer) + // for (int ch = 0; ch < channels; ch++) + // AudioSamples.MemCpy(s + ch * FlakeConstants.MAX_BLOCKSIZE, s + bs + ch * FlakeConstants.MAX_BLOCKSIZE, m_blockSize - bs); + } + + samplesInBuffer -= bs; + + return bs; + } + + public void Write(AudioBuffer buff) + { + if (!inited) + { + if (_IO == null) + _IO = new FileStream(_path, FileMode.Create, FileAccess.Write, FileShare.Read, 0x20000); + inited = true; + int header_size = flake_encode_init(); + _IO.Write(header, 0, header_size); + if (_IO.CanSeek) + first_frame_offset = _IO.Position; + } + + buff.Prepare(this); + + int pos = 0; + while (pos < buff.Length) + { + int block = Math.Min(buff.Length - pos, m_blockSize - samplesInBuffer); + + copy_samples(buff.Samples, pos, block); + + pos += block; + + while (samplesInBuffer >= m_blockSize) + output_frame(); + } + + if (md5 != null) + md5.TransformBlock(buff.Bytes, 0, buff.ByteLength, null, 0); + } + + public string Path { get { return _path; } } + + public static string Vendor + { + get + { + var version = typeof(AudioEncoder).Assembly.GetName().Version; + return vendor_string ?? "CUETools " + version.Major + "." + version.Minor + "." + version.Build; + } + set + { + vendor_string = value; + } + } + + static string vendor_string = null; + + int select_blocksize(int samplerate, int time_ms) + { + int blocksize = FlakeConstants.flac_blocksizes[1]; + int target = (samplerate * time_ms) / 1000; + if (eparams.variable_block_size > 0) + { + blocksize = 1024; + while (target >= blocksize) + blocksize <<= 1; + return blocksize >> 1; + } + + for (int i = 8; i < FlakeConstants.flac_blocksizes.Length - 1; i++) + if (target >= FlakeConstants.flac_blocksizes[i] && FlakeConstants.flac_blocksizes[i] > blocksize) + { + blocksize = FlakeConstants.flac_blocksizes[i]; + } + return blocksize; + } + + void write_streaminfo(byte[] header, int pos, int last) + { + Array.Clear(header, pos, 38); + BitWriter bitwriter = new BitWriter(header, pos, 38); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, (int)MetadataType.StreamInfo); + bitwriter.writebits(24, 34); + + if (eparams.variable_block_size > 0) + bitwriter.writebits(16, 0); + else + bitwriter.writebits(16, m_blockSize); + + bitwriter.writebits(16, m_blockSize); + bitwriter.writebits(24, 0); + bitwriter.writebits(24, max_frame_size); + bitwriter.writebits(20, Settings.PCM.SampleRate); + bitwriter.writebits(3, channels - 1); + bitwriter.writebits(5, Settings.PCM.BitsPerSample - 1); + + // total samples + if (sample_count > 0) + { + bitwriter.writebits(4, 0); + bitwriter.writebits(32, sample_count); + } + else + { + bitwriter.writebits(4, 0); + bitwriter.writebits(32, 0); + } + bitwriter.flush(); + } + + /** + * Write vorbis comment metadata block to byte array. + * Just writes the vendor string for now. + */ + int write_vorbis_comment(byte[] comment, int pos, int len, int last) + { + BitWriter bitwriter = new BitWriter(comment, pos, len); + Encoding enc = new UTF8Encoding(); + byte[] str = enc.GetBytes(Vendor); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, (int)MetadataType.VorbisComment); + int tagsLen = 0; + if (m_settings.Tags != null) + foreach (var t in m_settings.Tags) + tagsLen += 4 + enc.GetByteCount(t); + bitwriter.writebits(24, 8 + str.Length + tagsLen); + for (int i = 0; i < 4; i++) + bitwriter.writebits(8, (str.Length >> (i * 8)) & 0xff); + bitwriter.write(str); + int nTags = m_settings.Tags != null ? m_settings.Tags.Length : 0; + for (int i = 0; i < 4; i++) + bitwriter.writebits(8, (nTags >> (i * 8)) & 0xff); + if (m_settings.Tags != null) + foreach (var tag in m_settings.Tags) + { + str = enc.GetBytes(tag); + for (int i = 0; i < 4; i++) + bitwriter.writebits(8, (str.Length >> (i * 8)) & 0xff); + bitwriter.write(str); + } + bitwriter.flush(); + return bitwriter.Length; + } + + int write_seekpoints(byte[] header, int pos, int last) + { + seek_table_offset = pos + 4; + + BitWriter bitwriter = new BitWriter(header, pos, 4 + 18 * seek_table.Length); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, (int)MetadataType.Seektable); + bitwriter.writebits(24, 18 * seek_table.Length); + for (int i = 0; i < seek_table.Length; i++) + { + bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN, (ulong)seek_table[i].number); + bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN, (ulong)seek_table[i].offset); + bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN, seek_table[i].framesize); + } + bitwriter.flush(); + return 4 + 18 * seek_table.Length; + } + + /** + * Write padding metadata block to byte array. + */ + int + write_padding(byte[] padding, int pos, int last, int padlen) + { + BitWriter bitwriter = new BitWriter(padding, pos, 4); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, (int)MetadataType.Padding); + bitwriter.writebits(24, padlen); + + bitwriter.flush(); + return padlen + 4; + } + + int write_headers() + { + int header_size = 0; + int last = 0; + + // stream marker + header[0] = 0x66; + header[1] = 0x4C; + header[2] = 0x61; + header[3] = 0x43; + header_size += 4; + + // streaminfo + write_streaminfo(header, header_size, last); + header_size += 38; + + // seek table + if (_IO.CanSeek && seek_table != null) + header_size += write_seekpoints(header, header_size, last); + + // vorbis comments + if (m_settings.Padding == 0) last = 1; + header_size += write_vorbis_comment(header, header_size, header.Length - header_size, last); + + // padding + if (m_settings.Padding > 0) + { + last = 1; + header_size += write_padding(header, header_size, last, m_settings.Padding); + } + + return header_size; + } + + int flake_encode_init() + { + int i, header_len; + + //if(flake_validate_params(s) < 0) + + ch_code = channels - 1; + + // find samplerate in table + for (i = 1; i < 12; i++) + { + if (Settings.PCM.SampleRate == FlakeConstants.flac_samplerates[i]) + { + sr_code0 = i; + break; + } + } + + // if not in table, samplerate is non-standard + if (i == 12) + throw new Exception("non-standard samplerate"); + + for (i = 1; i < 8; i++) + { + if (Settings.PCM.BitsPerSample == FlakeConstants.flac_bitdepths[i]) + { + bps_code = i; + break; + } + } + if (i == 8) + throw new Exception("non-standard bps"); + + m_blockSize = m_settings.BlockSize != 0 ? m_settings.BlockSize : + select_blocksize(Settings.PCM.SampleRate, eparams.block_time_ms); + + // set maximum encoded frame size (if larger, re-encodes in verbatim mode) + if (channels == 2) + max_frame_size = 16 + ((m_blockSize * (Settings.PCM.BitsPerSample + Settings.PCM.BitsPerSample + 1) + 7) >> 3); + else + max_frame_size = 16 + ((m_blockSize * channels * Settings.PCM.BitsPerSample + 7) >> 3); + + if (_IO.CanSeek && eparams.do_seektable && sample_count > 0) + { + int seek_points_distance = Settings.PCM.SampleRate * 10; + int num_seek_points = 1 + sample_count / seek_points_distance; // 1 seek point per 10 seconds + if (sample_count % seek_points_distance == 0) + num_seek_points--; + seek_table = new SeekPoint[num_seek_points]; + for (int sp = 0; sp < num_seek_points; sp++) + { + seek_table[sp].framesize = 0; + seek_table[sp].offset = 0; + seek_table[sp].number = sp * seek_points_distance; + } + } + + // output header bytes + int tagsLen = 0; + Encoding enc = new UTF8Encoding(); + if (m_settings.Tags != null) + foreach (var t in m_settings.Tags) + tagsLen += 4 + enc.GetByteCount(t); + header = new byte[m_settings.Padding + 1024 + (seek_table == null ? 0 : seek_table.Length * 18) + tagsLen]; + header_len = write_headers(); + + // initialize CRC & MD5 + if (_IO.CanSeek && m_settings.DoMD5) + md5 = new MD5CryptoServiceProvider(); + + if (m_settings.DoVerify) + { + verify = new AudioDecoder(Settings.PCM); + verifyBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * channels]; + } + + frame_buffer = new byte[max_frame_size]; + + return header_len; + } + } + + struct FlakeEncodeParams + { + // prediction order selection method + // set by user prior to calling flake_encode_init + // if set to less than 0, it is chosen based on compression. + // valid values are 0 to 5 + // 0 = use maximum order only + // 1 = use estimation + // 2 = 2-level + // 3 = 4-level + // 4 = 8-level + // 5 = full search + // 6 = log search + public OrderMethod order_method; + + // block time in milliseconds + // set by the user prior to calling flake_encode_init + // used to calculate block_size based on sample rate + // can also be changed by user before encoding a frame + public int block_time_ms; + + // whether to use variable block sizes + // set by user prior to calling flake_encode_init + // 0 = fixed block size + // 1 = variable block size + public int variable_block_size; + + public bool do_seektable; + + public int development_mode; + + public int flake_set_defaults(EncoderSettings settings) + { + order_method = OrderMethod.Akaike; + block_time_ms = 105; + variable_block_size = 0; + do_seektable = true; + development_mode = -1; + + if (settings.GetEncoderModeIndex() == 11) + variable_block_size = 4; + + return 0; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/COPYING b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/COPYING new file mode 100644 index 000000000..69352d1d3 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/COPYING @@ -0,0 +1,503 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj new file mode 100644 index 000000000..a7db18bb0 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/CUETools.Codecs.Flake.csproj @@ -0,0 +1,29 @@ + + + + netstandard2.0 + 2.1.9.0 + CUETools.Codecs.Flake + CUETools.Codecs.Flake + CUETools + A library for encoding and decoding FLAC. + Copyright (c) 2008-2021 Grigory Chudov + Grigory Chudov + true + ..\bin\$(Configuration)\plugins + https://github.com/gchudov/cuetools.net + git + + + + + + False + + + + + + + + diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/ChannelMode.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/ChannelMode.cs new file mode 100644 index 000000000..e2544a336 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/ChannelMode.cs @@ -0,0 +1,11 @@ +namespace CUETools.Codecs.Flake +{ + public enum ChannelMode + { + NotStereo = 0, + LeftRight = 1, + LeftSide = 8, + RightSide = 9, + MidSide = 10 + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/DecoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/DecoderSettings.cs new file mode 100644 index 000000000..dea9e8865 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/DecoderSettings.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace CUETools.Codecs.Flake +{ + [JsonObject(MemberSerialization.OptIn)] + public class DecoderSettings : IAudioDecoderSettings + { + #region IAudioDecoderSettings implementation + [Browsable(false)] + public string Extension => "flac"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type DecoderType => typeof(AudioDecoder); + + [Browsable(false)] + public int Priority => 2; + + public IAudioDecoderSettings Clone() + { + return MemberwiseClone() as IAudioDecoderSettings; + } + #endregion + + public DecoderSettings() + { + this.Init(); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/EncoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/EncoderSettings.cs new file mode 100644 index 000000000..ba782b6e4 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/EncoderSettings.cs @@ -0,0 +1,271 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace CUETools.Codecs.Flake +{ + [JsonObject(MemberSerialization.OptIn)] + public class EncoderSettings : IAudioEncoderSettings + { + #region IAudioEncoderSettings implementation + [Browsable(false)] + public string Extension => "flac"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type EncoderType => typeof(AudioEncoder); + + [Browsable(false)] + public bool Lossless => true; + + [Browsable(false)] + public int Priority => 4; + + [Browsable(false)] + public string SupportedModes => this.AllowNonSubset || (this.PCM != null && this.PCM.SampleRate > 48000) ? "0 1 2 3 4 5 6 7 8 9 10 11" : "0 1 2 3 4 5 6 7 8"; + + [Browsable(false)] + public string DefaultMode => "5"; + + [Browsable(false)] + [DefaultValue("")] + [JsonProperty] + public string EncoderMode { get; set; } + + [Browsable(false)] + public AudioPCMConfig PCM { get; set; } + + [Browsable(false)] + public int BlockSize { get; set; } + + [Browsable(false)] + [DefaultValue(4096)] + public int Padding { get; set; } + + public IAudioEncoderSettings Clone() + { + return MemberwiseClone() as IAudioEncoderSettings; + } + #endregion + + public EncoderSettings() + { + this.Init(); + } + + public bool IsSubset() + { + return (BlockSize == 0 || (BlockSize <= 16384 && (PCM.SampleRate > 48000 || BlockSize <= 4608))) + && (PCM.SampleRate > 48000 || MaxLPCOrder <= 12) + && MaxPartitionOrder <= 8 + ; + //The blocksize bits in the frame header must be 0001-1110. The blocksize must be <=16384; if the sample rate is <= 48000Hz, the blocksize must be <=4608. + //The sample rate bits in the frame header must be 0001-1110. + //The bits-per-sample bits in the frame header must be 001-111. + //If the sample rate is <= 48000Hz, the filter order in LPC subframes must be less than or equal to 12, i.e. the subframe type bits in the subframe header may not be 101100-111111. + //The Rice partition order in a Rice-coded residual section must be less than or equal to 8. + } + + public void Validate() + { + if (this.GetEncoderModeIndex() < 0) + throw new Exception("unsupported encoder mode"); + this.SetDefaultValuesForMode(); + if (Padding < 0) + throw new Exception("unsupported padding value " + Padding.ToString()); + if (BlockSize != 0 && (BlockSize < 256 || BlockSize >= FlakeConstants.MAX_BLOCKSIZE)) + throw new Exception("unsupported block size " + BlockSize.ToString()); + if (MinLPCOrder > MaxLPCOrder || MaxLPCOrder > lpc.MAX_LPC_ORDER) + throw new Exception("invalid MaxLPCOrder " + MaxLPCOrder.ToString()); + if (MinFixedOrder < 0 || MinFixedOrder > 4) + throw new Exception("invalid MinFixedOrder " + MinFixedOrder.ToString()); + if (MaxFixedOrder < 0 || MaxFixedOrder > 4) + throw new Exception("invalid MaxFixedOrder " + MaxFixedOrder.ToString()); + if (MinPartitionOrder < 0) + throw new Exception("invalid MinPartitionOrder " + MinPartitionOrder.ToString()); + if (MinPartitionOrder > MaxPartitionOrder || MaxPartitionOrder > 8) + throw new Exception("invalid MaxPartitionOrder " + MaxPartitionOrder.ToString()); + if (PredictionType == PredictionType.None) + throw new Exception("invalid PredictionType " + PredictionType.ToString()); + if (PredictionType != PredictionType.Fixed) + { + if (WindowMethod == WindowMethod.Invalid) + throw new InvalidOperationException("invalid WindowMethod " + WindowMethod.ToString()); + if (WindowFunctions == WindowFunction.None) + throw new InvalidOperationException("invalid WindowFunctions " + WindowFunctions.ToString()); + if (EstimationDepth > 32 || EstimationDepth < 1) + throw new InvalidOperationException("invalid EstimationDepth " + EstimationDepth.ToString()); + if (MinPrecisionSearch < 0 || MinPrecisionSearch >= lpc.MAX_LPC_PRECISIONS) + throw new Exception("unsupported MinPrecisionSearch value"); + if (MaxPrecisionSearch < 0 || MaxPrecisionSearch >= lpc.MAX_LPC_PRECISIONS) + throw new Exception("unsupported MaxPrecisionSearch value"); + if (MaxPrecisionSearch < MinPrecisionSearch) + throw new Exception("unsupported MaxPrecisionSearch value"); + } + if (!AllowNonSubset && !IsSubset()) + throw new Exception("the encoding parameters specified do not conform to the FLAC Subset"); + } + + [DefaultValue(-1)] + [DefaultValueForMode(2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0)] + [Browsable(false)] + [DisplayName("MinFixedOrder")] + [SRDescription(typeof(Properties.Resources), "MinFixedOrderDescription")] + public int MinFixedOrder { get; set; } + + [DefaultValue(-1)] + [DefaultValueForMode(2, 4, 4, 4, 2, 2, 4, 4, 4, 4, 4, 4)] + [Browsable(false)] + [DisplayName("MaxFixedOrder")] + [SRDescription(typeof(Properties.Resources), "MaxFixedOrderDescription")] + public int MaxFixedOrder { get; set; } + + [DefaultValue(1)] + [Browsable(false)] + [DisplayName("MinLPCOrder")] + [SRDescription(typeof(Properties.Resources), "MinLPCOrderDescription")] + public int MinLPCOrder { get; set; } + + [DefaultValue(-1)] + [DefaultValueForMode(8, 8, 8, 12, 12, 12, 12, 12, 12, 32, 32, 32)] + [Browsable(false)] + [DisplayName("MaxLPCOrder")] + [SRDescription(typeof(Properties.Resources), "MaxLPCOrderDescription")] + public int MaxLPCOrder { get; set; } + + [DefaultValue(0)] + [DisplayName("MinPartitionOrder")] + [Browsable(false)] + [SRDescription(typeof(Properties.Resources), "MinPartitionOrderDescription")] + public int MinPartitionOrder { get; set; } + + [DefaultValue(-1)] + [DefaultValueForMode(6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 8)] + [DisplayName("MaxPartitionOrder")] + [Browsable(false)] + [SRDescription(typeof(Properties.Resources), "MaxPartitionOrderDescription")] + public int MaxPartitionOrder { get; set; } + + [DefaultValue(false)] + [DisplayName("Verify")] + [SRDescription(typeof(Properties.Resources), "DoVerifyDescription")] + [JsonProperty] + public bool DoVerify { get; set; } + + [DefaultValue(true)] + [DisplayName("MD5")] + [SRDescription(typeof(Properties.Resources), "DoMD5Description")] + [JsonProperty] + public bool DoMD5 { get; set; } + + [DefaultValue(false)] + [DisplayName("Allow Non-subset")] + [SRDescription(typeof(Properties.Resources), "AllowNonSubsetDescription")] + [JsonProperty] + public bool AllowNonSubset { get; set; } + + [DefaultValue(StereoMethod.Invalid)] + [DefaultValueForMode( + /* 0 */ StereoMethod.Independent, + /* 1 */ StereoMethod.EstimateFixed, + /* 2 */ StereoMethod.Estimate, + /* 3 */ StereoMethod.Estimate, + /* 4 */ StereoMethod.Evaluate, + /* 5 */ StereoMethod.Evaluate, + /* 6 */ StereoMethod.Evaluate, + /* 7 */ StereoMethod.Evaluate, + /* 8 */ StereoMethod.Evaluate, + /* 9 */ StereoMethod.Evaluate, + /* 10 */ StereoMethod.Evaluate, + /* 11 */ StereoMethod.Evaluate)] + [Browsable(false)] + public StereoMethod StereoMethod { get; set; } + + [DefaultValue(PredictionType.None)] + [DefaultValueForMode( + /* 0 */ PredictionType.Fixed, + /* 1 */ PredictionType.Fixed, + /* 2 */ PredictionType.Levinson, + /* 3 */ PredictionType.Levinson, + /* 4 */ PredictionType.Search, + /* 5 */ PredictionType.Search, + /* 6 */ PredictionType.Search, + /* 7 */ PredictionType.Search, + /* 8 */ PredictionType.Search, + /* 9 */ PredictionType.Levinson, + /* 10 */ PredictionType.Search, + /* 11 */ PredictionType.Search)] + [Browsable(false)] + public PredictionType PredictionType { get; set; } + + [DefaultValue(WindowMethod.Invalid)] + [DefaultValueForMode( + /* 0 */ WindowMethod.Invalid, + /* 1 */ WindowMethod.Invalid, + /* 2 */ WindowMethod.Estimate, + /* 3 */ WindowMethod.Estimate, + /* 4 */ WindowMethod.Estimate, + /* 5 */ WindowMethod.EvaluateN, + /* 6 */ WindowMethod.EvaluateN, + /* 7 */ WindowMethod.EvaluateN, + /* 8 */ WindowMethod.EvaluateN, + /* 9 */ WindowMethod.EvaluateN, + /* 10 */ WindowMethod.EvaluateN, + /* 11 */ WindowMethod.EvaluateN)] + [Browsable(false)] + public WindowMethod WindowMethod { get; set; } + + [DefaultValue(WindowFunction.None)] + [DefaultValueForMode( + /* 0 */ WindowFunction.None, + /* 1 */ WindowFunction.None, + /* 2 */ WindowFunction.Tukey3, + /* 3 */ WindowFunction.Tukey4, + /* 4 */ WindowFunction.Tukey4, + /* 5 */ WindowFunction.Tukey4 | WindowFunction.Tukey3, + /* 6 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey, + /* 7 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey, + /* 8 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey, + /* 9 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey, + /* 10 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey, + /* 11 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey)] + [Browsable(false)] + [DisplayName("WindowFunctions")] + [SRDescription(typeof(Properties.Resources), "WindowFunctionsDescription")] + public WindowFunction WindowFunctions { get; set; } + + [DefaultValue(0)] + [DefaultValueForMode(0, 0, 1, 1, 1, 1, 1, 1, 3, 1, 1, 5)] + [Browsable(false)] + public int EstimationDepth { get; set; } + + [DefaultValue(-1)] + [DefaultValueForMode(1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1)] + [Browsable(false)] + public int MinPrecisionSearch { get; set; } + + [DefaultValue(-1)] + [DefaultValueForMode(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)] + [Browsable(false)] + public int MaxPrecisionSearch { get; set; } + + [DefaultValue(0)] + [Browsable(false)] + public int TukeyParts { get; set; } + + [DefaultValue(1.0)] + [Browsable(false)] + public double TukeyOverlap { get; set; } + + [DefaultValue(1.0)] + [Browsable(false)] + public double TukeyP { get; set; } + + [Browsable(false)] + public string[] Tags { get; set; } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacFrame.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacFrame.cs new file mode 100644 index 000000000..9397fdd99 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacFrame.cs @@ -0,0 +1,96 @@ +namespace CUETools.Codecs.Flake +{ + unsafe public class FlacFrame + { + public int blocksize; + public int bs_code0, bs_code1; + public ChannelMode ch_mode; + //public int ch_order0, ch_order1; + public byte crc8; + public FlacSubframeInfo[] subframes; + public int frame_number; + public FlacSubframe current; + public float* window_buffer; + public int nSeg = 0; + + public BitWriter writer = null; + public int writer_offset = 0; + + public FlacFrame(int subframes_count) + { + subframes = new FlacSubframeInfo[subframes_count]; + for (int ch = 0; ch < subframes_count; ch++) + subframes[ch] = new FlacSubframeInfo(); + current = new FlacSubframe(); + } + + public void InitSize(int bs, bool vbs) + { + blocksize = bs; + int i = 15; + if (!vbs) + { + for (i = 0; i < 15; i++) + { + if (bs == FlakeConstants.flac_blocksizes[i]) + { + bs_code0 = i; + bs_code1 = -1; + break; + } + } + } + if (i == 15) + { + if (blocksize <= 256) + { + bs_code0 = 6; + bs_code1 = blocksize - 1; + } + else + { + bs_code0 = 7; + bs_code1 = blocksize - 1; + } + } + } + + public void ChooseBestSubframe(int ch) + { + if (current.size >= subframes[ch].best.size) + return; + FlacSubframe tmp = subframes[ch].best; + subframes[ch].best = current; + current = tmp; + } + + public void SwapSubframes(int ch1, int ch2) + { + FlacSubframeInfo tmp = subframes[ch1]; + subframes[ch1] = subframes[ch2]; + subframes[ch2] = tmp; + } + + /// + /// Swap subframes according to channel mode. + /// It is assumed that we have 4 subframes, + /// 0 is right, 1 is left, 2 is middle, 3 is difference + /// + public void ChooseSubframes() + { + switch (ch_mode) + { + case ChannelMode.MidSide: + SwapSubframes(0, 2); + SwapSubframes(1, 3); + break; + case ChannelMode.RightSide: + SwapSubframes(0, 3); + break; + case ChannelMode.LeftSide: + SwapSubframes(1, 3); + break; + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframe.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframe.cs new file mode 100644 index 000000000..4b69fd758 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframe.cs @@ -0,0 +1,21 @@ +namespace CUETools.Codecs.Flake +{ + unsafe public class FlacSubframe + { + public FlacSubframe() + { + rc = new RiceContext(); + coefs = new int[lpc.MAX_LPC_ORDER]; + } + public SubframeType type; + public int order; + public int* residual; + public RiceContext rc; + public uint size; + + public int cbits; + public int shift; + public int[] coefs; + public int window; + }; +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframeInfo.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframeInfo.cs new file mode 100644 index 000000000..4f6bd26de --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/FlacSubframeInfo.cs @@ -0,0 +1,45 @@ +using System; + +namespace CUETools.Codecs.Flake +{ + unsafe public class FlacSubframeInfo + { + public FlacSubframeInfo() + { + best = new FlacSubframe(); + sf = new LpcSubframeInfo(); + best_fixed = new ulong[5]; + lpc_ctx = new LpcContext[lpc.MAX_LPC_WINDOWS]; + for (int i = 0; i < lpc.MAX_LPC_WINDOWS; i++) + lpc_ctx[i] = new LpcContext(); + } + + public void Init(int* s, int* r, int bps, int w) + { + if (w > bps) + throw new Exception("internal error"); + samples = s; + obits = bps - w; + wbits = w; + for (int o = 0; o <= 4; o++) + best_fixed[o] = 0; + best.residual = r; + best.type = SubframeType.Verbatim; + best.size = AudioSamples.UINT32_MAX; + sf.Reset(); + for (int iWindow = 0; iWindow < lpc.MAX_LPC_WINDOWS; iWindow++) + lpc_ctx[iWindow].Reset(); + //sf.obits = obits; + done_fixed = 0; + } + + public FlacSubframe best; + public int obits; + public int wbits; + public int* samples; + public uint done_fixed; + public ulong[] best_fixed; + public LpcContext[] lpc_ctx; + public LpcSubframeInfo sf; + }; +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Flake.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Flake.cs new file mode 100644 index 000000000..b4a33d9d2 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Flake.cs @@ -0,0 +1,74 @@ +/** + * CUETools.Flake: pure managed FLAC audio encoder + * Copyright (c) 2009-2021 Grigory Chudov + * Based on Flake encoder, http://flake-enc.sourceforge.net/ + * Copyright (c) 2006-2009 Justin Ruggles + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +using System; + +namespace CUETools.Codecs.Flake +{ + public class FlakeConstants + { + public const int MAX_BLOCKSIZE = 65535; + public const int MAX_RICE_PARAM = 14; + public const int MAX_PARTITION_ORDER = 8; + public const int MAX_PARTITIONS = 1 << MAX_PARTITION_ORDER; + + public const int FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN = 64; /* bits */ + public const int FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN = 64; /* bits */ + public const int FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN = 16; /* bits */ + + public static readonly int[] flac_samplerates = new int[16] { + 0, 88200, 176400, 192000, + 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, + 0, 0, 0, 0 + }; + //1100 : get 8 bit sample rate (in kHz) from end of header + //1101 : get 16 bit sample rate (in Hz) from end of header + //1110 : get 16 bit sample rate (in tens of Hz) from end of header + public static readonly int[] flac_blocksizes = new int[15] { 0, 192, 576, 1152, 2304, 4608, 0, 0, 256, 512, 1024, 2048, 4096, 8192, 16384 }; + //0110 : get 8 bit (blocksize-1) from end of header + //0111 : get 16 bit (blocksize-1) from end of header + public static readonly int[] flac_bitdepths = new int[8] { 0, 8, 12, 0, 16, 20, 24, 0 }; + + public static PredictionType LookupPredictionType(string name) + { + return (PredictionType)(Enum.Parse(typeof(PredictionType), name, true)); + } + + public static StereoMethod LookupStereoMethod(string name) + { + return (StereoMethod)(Enum.Parse(typeof(StereoMethod), name, true)); + } + + public static WindowMethod LookupWindowMethod(string name) + { + return (WindowMethod)(Enum.Parse(typeof(WindowMethod), name, true)); + } + + public static OrderMethod LookupOrderMethod(string name) + { + return (OrderMethod)(Enum.Parse(typeof(OrderMethod), name, true)); + } + + public static WindowFunction LookupWindowFunction(string name) + { + return (WindowFunction)(Enum.Parse(typeof(WindowFunction), name, true)); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/MetadataType.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/MetadataType.cs new file mode 100644 index 000000000..8f7f7055d --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/MetadataType.cs @@ -0,0 +1,45 @@ +namespace CUETools.Codecs.Flake +{ + public enum MetadataType + { + /// + /// STREAMINFO block + /// + StreamInfo = 0, + + /// + /// PADDING block + /// + Padding = 1, + + /// + /// APPLICATION block + /// + Application = 2, + + /// + /// SEEKTABLE block + /// + Seektable = 3, + + /// + /// VORBISCOMMENT block (a.k.a. FLAC tags) + /// + VorbisComment = 4, + + /// + /// CUESHEET block + /// + CUESheet = 5, + + /// + /// PICTURE block + /// + Picture = 6, + + /// + /// marker to denote beginning of undefined type range; this number will increase as new metadata types are added + /// + Undefined = 7 + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/OrderMethod.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/OrderMethod.cs new file mode 100644 index 000000000..25f38ba49 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/OrderMethod.cs @@ -0,0 +1,10 @@ +namespace CUETools.Codecs.Flake +{ + public enum OrderMethod + { + /// + /// Select orders based on Akaike's criteria + /// + Akaike = 0 + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/PredictionType.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/PredictionType.cs new file mode 100644 index 000000000..d41ef8257 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/PredictionType.cs @@ -0,0 +1,25 @@ +namespace CUETools.Codecs.Flake +{ + /// + /// Type of linear prediction + /// + public enum PredictionType + { + /// + /// Verbatim + /// + None = 0, + /// + /// Fixed prediction only + /// + Fixed = 1, + /// + /// Levinson-Durbin recursion + /// + Levinson = 2, + /// + /// Exhaustive search + /// + Search = 3 + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.Designer.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.Designer.cs new file mode 100644 index 000000000..dab1dc995 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18033 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CUETools.Codecs.Flake.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CUETools.Codecs.Flake.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Allow non-subset modes, which allow for greater compression, but are less compatible. + /// + internal static string AllowNonSubsetDescription { + get { + return ResourceManager.GetString("AllowNonSubsetDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Calculate MD5 hash for audio stream. + /// + internal static string DoMD5Description { + get { + return ResourceManager.GetString("DoMD5Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decode each frame and compare with original. + /// + internal static string DoVerifyDescription { + get { + return ResourceManager.GetString("DoVerifyDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Samples written differs from the expected sample count. + /// + internal static string ExceptionSampleCount { + get { + return ResourceManager.GetString("ExceptionSampleCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validation failed. + /// + internal static string ExceptionValidationFailed { + get { + return ResourceManager.GetString("ExceptionValidationFailed", resourceCulture); + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.de-DE.resx b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.de-DE.resx new file mode 100644 index 000000000..40b3cd6dd --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.de-DE.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Non-subset Modi erlauben, die eine höhere Kompression ermöglichen, allerdings weniger kompatibel sind + + + MD5-Prüfsumme für Audiodaten berechnen + + + Jeden Frame dekodieren und mit dem Original vergleichen + + + Die geschriebenen Samples unterscheiden sich von der erwarteten Anzahl + + + Validierung fehlgeschlagen + + \ No newline at end of file diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.resx b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.resx new file mode 100644 index 000000000..1fdb388fc --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Allow non-subset modes, which allow for greater compression, but are less compatible + + + Calculate MD5 hash for audio stream + + + Decode each frame and compare with original + + + Samples written differs from the expected sample count + + + Validation failed + + \ No newline at end of file diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.ru-RU.resx b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.ru-RU.resx new file mode 100644 index 000000000..cbe431646 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/Properties/Resources.ru-RU.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Вычислять MD5-хеш аудиопотока + + + Декодировать каждый кадр и сравнивать с оригиналом + + + Количество записанных сэмплов отличается от ожидаемого + + + Ошибка верификации + + + Разрешить non-subset режимы допускающие большее сжатие, +но меньшую совместимость с декодерами + + \ No newline at end of file diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/RiceContext.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/RiceContext.cs new file mode 100644 index 000000000..c00aafbdc --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/RiceContext.cs @@ -0,0 +1,30 @@ +namespace CUETools.Codecs.Flake +{ + unsafe public class RiceContext + { + public RiceContext() + { + rparams = new int[FlakeConstants.MAX_PARTITIONS]; + esc_bps = new int[FlakeConstants.MAX_PARTITIONS]; + } + /// + /// partition order + /// + public int porder; + + /// + /// coding method: rice parameters use 4 bits for coding_method 0 and 5 bits for coding_method 1 + /// + public int coding_method; + + /// + /// Rice parameters + /// + public int[] rparams; + + /// + /// bps if using escape code + /// + public int[] esc_bps; + }; +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SeekPoint.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SeekPoint.cs new file mode 100644 index 000000000..f17bd7429 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SeekPoint.cs @@ -0,0 +1,9 @@ +namespace CUETools.Codecs.Flake +{ + public struct SeekPoint + { + public long number; + public long offset; + public int framesize; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/StereoMethod.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/StereoMethod.cs new file mode 100644 index 000000000..ddcc61d10 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/StereoMethod.cs @@ -0,0 +1,15 @@ + +namespace CUETools.Codecs.Flake +{ + public enum StereoMethod + { + Invalid, + Independent, + Estimate, + Evaluate, + Search, + EstimateX, + EvaluateX, + EstimateFixed, + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SubframeType.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SubframeType.cs new file mode 100644 index 000000000..3c5cadda5 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/SubframeType.cs @@ -0,0 +1,10 @@ +namespace CUETools.Codecs.Flake +{ + public enum SubframeType + { + Constant = 0, + Verbatim = 1, + Fixed = 8, + LPC = 32 + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowFunction.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowFunction.cs new file mode 100644 index 000000000..9723b56d4 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowFunction.cs @@ -0,0 +1,27 @@ +namespace CUETools.Codecs.Flake +{ + public enum WindowFunction + { + None = 0, + Welch = 1, + Tukey = 2, + Hann = 4, + Flattop = 8, + Bartlett = 16, + Tukey2 = 32, + Tukey3 = 64, + Tukey4 = (1 << 7), + Tukey2A = (1 << 9), + Tukey2B = (1 << 10), + Tukey3A = (1 << 11), + Tukey3B = (1 << 12), + Tukey4A = (1 << 13), + Tukey4B = (1 << 14), + Tukey1A = (1 << 15), + Tukey1B = (1 << 16), + Tukey1X = (1 << 17), + Tukey2X = (1 << 18), + Tukey3X = (1 << 19), + Tukey4X = (1 << 20), + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowMethod.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowMethod.cs new file mode 100644 index 000000000..de4cc239a --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs.Flake/WindowMethod.cs @@ -0,0 +1,18 @@ +namespace CUETools.Codecs.Flake +{ + public enum WindowMethod + { + Invalid, + Evaluate, + Search, + Estimate, + Estimate2, + Estimate3, + EstimateN, + Evaluate2, + Evaluate2N, + Evaluate3, + Evaluate3N, + EvaluateN, + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioBuffer.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioBuffer.cs new file mode 100644 index 000000000..88fdce6b6 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioBuffer.cs @@ -0,0 +1,484 @@ +using System; + +namespace CUETools.Codecs +{ + public class AudioBuffer + { + #region Static Methods + + public static unsafe void FLACSamplesToBytes_16(int[,] inSamples, int inSampleOffset, + byte* outSamples, int sampleCount, int channelCount) + { + int loopCount = sampleCount * channelCount; + + if (inSamples.GetLength(0) - inSampleOffset < sampleCount) + throw new IndexOutOfRangeException(); + + fixed (int* pInSamplesFixed = &inSamples[inSampleOffset, 0]) + { + int* pInSamples = pInSamplesFixed; + short* pOutSamples = (short*)outSamples; + for (int i = 0; i < loopCount; i++) + pOutSamples[i] = (short)pInSamples[i]; + //*(pOutSamples++) = (short)*(pInSamples++); + } + } + + public static unsafe void FLACSamplesToBytes_16(int[,] inSamples, int inSampleOffset, + byte[] outSamples, int outByteOffset, int sampleCount, int channelCount) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.GetLength(0) - inSampleOffset < sampleCount) || + (outSamples.Length - outByteOffset < loopCount * 2)) + { + throw new IndexOutOfRangeException(); + } + + fixed (byte* pOutSamplesFixed = &outSamples[outByteOffset]) + FLACSamplesToBytes_16(inSamples, inSampleOffset, pOutSamplesFixed, sampleCount, channelCount); + } + + public static unsafe void FLACSamplesToBytes_24(int[,] inSamples, int inSampleOffset, + byte[] outSamples, int outByteOffset, int sampleCount, int channelCount, int wastedBits) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.GetLength(0) - inSampleOffset < sampleCount) || + (outSamples.Length - outByteOffset < loopCount * 3)) + { + throw new IndexOutOfRangeException(); + } + + fixed (int* pInSamplesFixed = &inSamples[inSampleOffset, 0]) + { + fixed (byte* pOutSamplesFixed = &outSamples[outByteOffset]) + { + int* pInSamples = pInSamplesFixed; + byte* pOutSamples = pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + uint sample_out = (uint)*(pInSamples++) << wastedBits; + *(pOutSamples++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(pOutSamples++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(pOutSamples++) = (byte)(sample_out & 0xFF); + } + } + } + } + + public static unsafe void FloatToBytes_16(float[,] inSamples, int inSampleOffset, + byte[] outSamples, int outByteOffset, int sampleCount, int channelCount) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.GetLength(0) - inSampleOffset < sampleCount) || + (outSamples.Length - outByteOffset < loopCount * 2)) + { + throw new IndexOutOfRangeException(); + } + + fixed (float* pInSamplesFixed = &inSamples[inSampleOffset, 0]) + { + fixed (byte* pOutSamplesFixed = &outSamples[outByteOffset]) + { + float* pInSamples = pInSamplesFixed; + short* pOutSamples = (short*)pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + *(pOutSamples++) = (short)(32758 * (*(pInSamples++))); + } + } + } + } + + public static unsafe void FloatToBytes(float[,] inSamples, int inSampleOffset, + byte[] outSamples, int outByteOffset, int sampleCount, int channelCount, int bitsPerSample) + { + if (bitsPerSample == 16) + FloatToBytes_16(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount); + //else if (bitsPerSample > 16 && bitsPerSample <= 24) + // FLACSamplesToBytes_24(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount, 24 - bitsPerSample); + else if (bitsPerSample == 32) + Buffer.BlockCopy(inSamples, inSampleOffset * 4 * channelCount, outSamples, outByteOffset, sampleCount * 4 * channelCount); + else + throw new Exception("Unsupported bitsPerSample value"); + } + + public static unsafe void FLACSamplesToBytes(int[,] inSamples, int inSampleOffset, + byte[] outSamples, int outByteOffset, int sampleCount, int channelCount, int bitsPerSample) + { + if (bitsPerSample == 16) + FLACSamplesToBytes_16(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount); + else if (bitsPerSample > 16 && bitsPerSample <= 24) + FLACSamplesToBytes_24(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount, 24 - bitsPerSample); + else + throw new Exception("Unsupported bitsPerSample value"); + } + + public static unsafe void FLACSamplesToBytes(int[,] inSamples, int inSampleOffset, + byte* outSamples, int sampleCount, int channelCount, int bitsPerSample) + { + if (bitsPerSample == 16) + FLACSamplesToBytes_16(inSamples, inSampleOffset, outSamples, sampleCount, channelCount); + else + throw new Exception("Unsupported bitsPerSample value"); + } + + public static unsafe void Bytes16ToFloat(byte[] inSamples, int inByteOffset, + float[,] outSamples, int outSampleOffset, int sampleCount, int channelCount) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.Length - inByteOffset < loopCount * 2) || + (outSamples.GetLength(0) - outSampleOffset < sampleCount)) + throw new IndexOutOfRangeException(); + + fixed (byte* pInSamplesFixed = &inSamples[inByteOffset]) + { + fixed (float* pOutSamplesFixed = &outSamples[outSampleOffset, 0]) + { + short* pInSamples = (short*)pInSamplesFixed; + float* pOutSamples = pOutSamplesFixed; + for (int i = 0; i < loopCount; i++) + *(pOutSamples++) = *(pInSamples++) / 32768.0f; + } + } + } + + public static unsafe void BytesToFLACSamples_16(byte[] inSamples, int inByteOffset, + int[,] outSamples, int outSampleOffset, int sampleCount, int channelCount) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.Length - inByteOffset < loopCount * 2) || + (outSamples.GetLength(0) - outSampleOffset < sampleCount)) + { + throw new IndexOutOfRangeException(); + } + + fixed (byte* pInSamplesFixed = &inSamples[inByteOffset]) + { + fixed (int* pOutSamplesFixed = &outSamples[outSampleOffset, 0]) + { + short* pInSamples = (short*)pInSamplesFixed; + int* pOutSamples = pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + *(pOutSamples++) = (int)*(pInSamples++); + } + } + } + } + + public static unsafe void BytesToFLACSamples_24(byte[] inSamples, int inByteOffset, + int[,] outSamples, int outSampleOffset, int sampleCount, int channelCount, int wastedBits) + { + int loopCount = sampleCount * channelCount; + + if ((inSamples.Length - inByteOffset < loopCount * 3) || + (outSamples.GetLength(0) - outSampleOffset < sampleCount)) + throw new IndexOutOfRangeException(); + + fixed (byte* pInSamplesFixed = &inSamples[inByteOffset]) + { + fixed (int* pOutSamplesFixed = &outSamples[outSampleOffset, 0]) + { + byte* pInSamples = (byte*)pInSamplesFixed; + int* pOutSamples = pOutSamplesFixed; + for (int i = 0; i < loopCount; i++) + { + int sample = (int)*(pInSamples++); + sample += (int)*(pInSamples++) << 8; + sample += (int)*(pInSamples++) << 16; + *(pOutSamples++) = (sample << 8) >> (8 + wastedBits); + } + } + } + } + + public static unsafe void BytesToFLACSamples(byte[] inSamples, int inByteOffset, + int[,] outSamples, int outSampleOffset, int sampleCount, int channelCount, int bitsPerSample) + { + if (bitsPerSample == 16) + BytesToFLACSamples_16(inSamples, inByteOffset, outSamples, outSampleOffset, sampleCount, channelCount); + else if (bitsPerSample > 16 && bitsPerSample <= 24) + BytesToFLACSamples_24(inSamples, inByteOffset, outSamples, outSampleOffset, sampleCount, channelCount, 24 - bitsPerSample); + else + throw new Exception("Unsupported bitsPerSample value"); + } + + #endregion + + private int[,] samples; + private float[,] fsamples; + private byte[] bytes; + private int length; + private int size; + private AudioPCMConfig pcm; + private bool dataInSamples = false; + private bool dataInBytes = false; + private bool dataInFloat = false; + + public int Length + { + get { return length; } + set { length = value; } + } + + public int Size + { + get { return size; } + } + + public AudioPCMConfig PCM { get { return pcm; } } + + public int ByteLength + { + get + { + return length * pcm.BlockAlign; + } + } + + public int[,] Samples + { + get + { + if (samples == null || samples.GetLength(0) < length) + samples = new int[size, pcm.ChannelCount]; + if (!dataInSamples && dataInBytes && length != 0) + BytesToFLACSamples(bytes, 0, samples, 0, length, pcm.ChannelCount, pcm.BitsPerSample); + dataInSamples = true; + return samples; + } + } + + public float[,] Float + { + get + { + if (fsamples == null || fsamples.GetLength(0) < length) + fsamples = new float[size, pcm.ChannelCount]; + if (!dataInFloat && dataInBytes && length != 0) + { + if (pcm.BitsPerSample == 16) + Bytes16ToFloat(bytes, 0, fsamples, 0, length, pcm.ChannelCount); + //else if (pcm.BitsPerSample > 16 && PCM.BitsPerSample <= 24) + // BytesToFLACSamples_24(bytes, 0, fsamples, 0, length, pcm.ChannelCount, 24 - pcm.BitsPerSample); + else if (pcm.BitsPerSample == 32) + Buffer.BlockCopy(bytes, 0, fsamples, 0, length * 4 * pcm.ChannelCount); + else + throw new Exception("Unsupported bitsPerSample value"); + } + dataInFloat = true; + return fsamples; + } + } + + public byte[] Bytes + { + get + { + if (bytes == null || bytes.Length < length * pcm.BlockAlign) + bytes = new byte[size * pcm.BlockAlign]; + if (!dataInBytes && length != 0) + { + if (dataInSamples) + FLACSamplesToBytes(samples, 0, bytes, 0, length, pcm.ChannelCount, pcm.BitsPerSample); + else if (dataInFloat) + FloatToBytes(fsamples, 0, bytes, 0, length, pcm.ChannelCount, pcm.BitsPerSample); + } + dataInBytes = true; + return bytes; + } + } + + public AudioBuffer(AudioPCMConfig _pcm, int _size) + { + pcm = _pcm; + size = _size; + length = 0; + } + + public AudioBuffer(AudioPCMConfig _pcm, int[,] _samples, int _length) + { + pcm = _pcm; + // assert _samples.GetLength(1) == pcm.ChannelCount + Prepare(_samples, _length); + } + + public AudioBuffer(AudioPCMConfig _pcm, byte[] _bytes, int _length) + { + pcm = _pcm; + Prepare(_bytes, _length); + } + + public AudioBuffer(IAudioSource source, int _size) + { + pcm = source.PCM; + size = _size; + } + + public void Prepare(IAudioDest dest) + { + if (dest.Settings.PCM.ChannelCount != pcm.ChannelCount || dest.Settings.PCM.BitsPerSample != pcm.BitsPerSample) + throw new Exception("AudioBuffer format mismatch"); + } + + public void Prepare(IAudioSource source, int maxLength) + { + if (source.PCM.ChannelCount != pcm.ChannelCount || source.PCM.BitsPerSample != pcm.BitsPerSample) + throw new Exception("AudioBuffer format mismatch"); + length = size; + if (maxLength >= 0) + length = Math.Min(length, maxLength); + if (source.Remaining >= 0) + length = (int)Math.Min((long)length, source.Remaining); + dataInBytes = false; + dataInSamples = false; + dataInFloat = false; + } + + public void Prepare(int maxLength) + { + length = size; + if (maxLength >= 0) + length = Math.Min(length, maxLength); + dataInBytes = false; + dataInSamples = false; + dataInFloat = false; + } + + public void Prepare(int[,] _samples, int _length) + { + length = _length; + size = _samples.GetLength(0); + samples = _samples; + dataInSamples = true; + dataInBytes = false; + dataInFloat = false; + if (length > size) + throw new Exception("Invalid length"); + } + + public void Prepare(byte[] _bytes, int _length) + { + length = _length; + size = _bytes.Length / PCM.BlockAlign; + bytes = _bytes; + dataInSamples = false; + dataInBytes = true; + dataInFloat = false; + if (length > size) + throw new Exception("Invalid length"); + } + + internal unsafe void Load(int dstOffset, AudioBuffer src, int srcOffset, int copyLength) + { + if (dataInBytes) + Buffer.BlockCopy(src.Bytes, srcOffset * pcm.BlockAlign, Bytes, dstOffset * pcm.BlockAlign, copyLength * pcm.BlockAlign); + if (dataInSamples) + Buffer.BlockCopy(src.Samples, srcOffset * pcm.ChannelCount * 4, Samples, dstOffset * pcm.ChannelCount * 4, copyLength * pcm.ChannelCount * 4); + if (dataInFloat) + Buffer.BlockCopy(src.Float, srcOffset * pcm.ChannelCount * 4, Float, dstOffset * pcm.ChannelCount * 4, copyLength * pcm.ChannelCount * 4); + } + + public unsafe void Prepare(AudioBuffer _src, int _offset, int _length) + { + length = Math.Min(size, _src.Length - _offset); + if (_length >= 0) + length = Math.Min(length, _length); + dataInBytes = false; + dataInFloat = false; + dataInSamples = false; + if (_src.dataInBytes) + dataInBytes = true; + else if (_src.dataInSamples) + dataInSamples = true; + else if (_src.dataInFloat) + dataInFloat = true; + Load(0, _src, _offset, length); + } + + public void Swap(AudioBuffer buffer) + { + if (pcm.BitsPerSample != buffer.PCM.BitsPerSample || pcm.ChannelCount != buffer.PCM.ChannelCount) + throw new Exception("AudioBuffer format mismatch"); + + int[,] samplesTmp = samples; + float[,] floatsTmp = fsamples; + byte[] bytesTmp = bytes; + + fsamples = buffer.fsamples; + samples = buffer.samples; + bytes = buffer.bytes; + length = buffer.length; + size = buffer.size; + dataInSamples = buffer.dataInSamples; + dataInBytes = buffer.dataInBytes; + dataInFloat = buffer.dataInFloat; + + buffer.samples = samplesTmp; + buffer.bytes = bytesTmp; + buffer.fsamples = floatsTmp; + buffer.length = 0; + buffer.dataInSamples = false; + buffer.dataInBytes = false; + buffer.dataInFloat = false; + } + + unsafe public void Interlace(int pos, int* src1, int* src2, int n) + { + if (PCM.ChannelCount != 2) + { + throw new Exception("Must be stereo"); + } + if (PCM.BitsPerSample == 16) + { + fixed (byte* bs = Bytes) + { + int* res = ((int*)bs) + pos; + for (int i = n; i > 0; i--) + *(res++) = (*(src1++) & 0xffff) ^ (*(src2++) << 16); + } + } + else if (PCM.BitsPerSample == 24) + { + fixed (byte* bs = Bytes) + { + byte* res = bs + pos * 6; + for (int i = n; i > 0; i--) + { + uint sample_out = (uint)*(src1++); + *(res++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(res++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(res++) = (byte)(sample_out & 0xFF); + sample_out = (uint)*(src2++); + *(res++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(res++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(res++) = (byte)(sample_out & 0xFF); + } + } + } + else + { + throw new Exception("Unsupported BPS"); + } + } + + //public void Clear() + //{ + // length = 0; + //} + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioDecoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioDecoderSettings.cs new file mode 100644 index 000000000..52a8234ce --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioDecoderSettings.cs @@ -0,0 +1,54 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Xml.Serialization; +using System.Text; +using Newtonsoft.Json; +using System.IO; + +namespace CUETools.Codecs +{ + public interface IAudioDecoderSettings + { + string Name { get; } + + string Extension { get; } + + Type DecoderType { get; } + + int Priority { get; } + + IAudioDecoderSettings Clone(); + } + + public static class IAudioDecoderSettingsExtensions + { + public static bool HasBrowsableAttributes(this IAudioDecoderSettings settings) + { + bool hasBrowsable = false; + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + { + bool isBrowsable = true; + foreach (var attribute in property.Attributes) + { + var browsable = attribute as BrowsableAttribute; + isBrowsable &= browsable == null || browsable.Browsable; + } + hasBrowsable |= isBrowsable; + } + return hasBrowsable; + } + + public static void Init(this IAudioDecoderSettings settings) + { + // Iterate through each property and call ResetValue() + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + property.ResetValue(settings); + } + + public static IAudioSource Open(this IAudioDecoderSettings settings, string path, Stream IO = null) + { + return Activator.CreateInstance(settings.DecoderType, settings, path, IO) as IAudioSource; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioEncoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioEncoderSettings.cs new file mode 100644 index 000000000..7d01b2f07 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioEncoderSettings.cs @@ -0,0 +1,124 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +#if NET20 + namespace System.Runtime.CompilerServices + { + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class + | AttributeTargets.Method)] + public sealed class ExtensionAttribute : Attribute { } + } +#endif + +namespace CUETools.Codecs +{ + public interface IAudioEncoderSettings + { + string Name { get; } + + string Extension { get; } + + Type EncoderType { get; } + + bool Lossless { get; } + + int Priority { get; } + + string SupportedModes { get; } + + string DefaultMode { get; } + + string EncoderMode { get; set; } + + AudioPCMConfig PCM { get; set; } + + int BlockSize { get; set; } + + int Padding { get; set; } + + IAudioEncoderSettings Clone(); + } + + public static class IAudioEncoderSettingsExtensions + { + public static bool HasBrowsableAttributes(this IAudioEncoderSettings settings) + { + bool hasBrowsable = false; + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + { + bool isBrowsable = true; + foreach (var attribute in property.Attributes) + { + var browsable = attribute as BrowsableAttribute; + isBrowsable &= browsable == null || browsable.Browsable; + } + hasBrowsable |= isBrowsable; + } + return hasBrowsable; + } + + public static int GetEncoderModeIndex(this IAudioEncoderSettings settings) + { + return new List(settings.SupportedModes.Split(' ')).FindIndex(m => m == settings.EncoderMode); + } + + public static void SetEncoderModeIndex(this IAudioEncoderSettings settings, int value) + { + string[] modes = settings.SupportedModes.Split(' '); + if (modes.Length == 0 && value < 0) + return; + if (value < 0 || value >= modes.Length) + throw new IndexOutOfRangeException(); + settings.EncoderMode = modes[value]; + } + + public static void SetDefaultValuesForMode(this IAudioEncoderSettings settings) + { + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + if (!property.CanResetValue(settings)) + foreach (var attribute in property.Attributes) + if (attribute is DefaultValueForModeAttribute) + { + var defaultValueForMode = attribute as DefaultValueForModeAttribute; + property.SetValue(settings, defaultValueForMode.m_values[settings.GetEncoderModeIndex()]); + } + } + + public static bool HasDefaultValuesForMode(this IAudioEncoderSettings settings, int index) + { + bool res = true; + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + foreach (var attribute in property.Attributes) + if (attribute is DefaultValueForModeAttribute) + { + var defaultValueForMode = attribute as DefaultValueForModeAttribute; + res &= property.GetValue(settings).Equals(defaultValueForMode.m_values[index]); + } + return res; + } + + public static int GuessEncoderMode(this IAudioEncoderSettings settings) + { + // return new List(settings.SupportedModes.Split(' ')).FindIndex(m => settings.HasDefaultValuesForMode(m)); + string[] modes = settings.SupportedModes.Split(' '); + if (modes == null || modes.Length < 1) + return -1; + for (int i = 0; i < modes.Length; i++) + if (settings.HasDefaultValuesForMode(i)) + return i; + return -1; + } + + public static void Init(this IAudioEncoderSettings settings, AudioPCMConfig pcm = null) + { + // Iterate through each property and call ResetValue() + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) + property.ResetValue(settings); + settings.EncoderMode = settings.DefaultMode; + settings.PCM = pcm; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPCMConfig.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPCMConfig.cs new file mode 100644 index 000000000..d0e2e72cd --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPCMConfig.cs @@ -0,0 +1,138 @@ +namespace CUETools.Codecs +{ + public class AudioPCMConfig + { + public static readonly AudioPCMConfig RedBook = new AudioPCMConfig(16, 2, 44100); + public enum SpeakerConfig + { + SPEAKER_FRONT_LEFT = 0x1, + SPEAKER_FRONT_RIGHT = 0x2, + SPEAKER_FRONT_CENTER = 0x4, + SPEAKER_LOW_FREQUENCY = 0x8, + SPEAKER_BACK_LEFT = 0x10, + SPEAKER_BACK_RIGHT = 0x20, + SPEAKER_FRONT_LEFT_OF_CENTER = 0x40, + SPEAKER_FRONT_RIGHT_OF_CENTER = 0x80, + SPEAKER_BACK_CENTER = 0x100, + SPEAKER_SIDE_LEFT = 0x200, + SPEAKER_SIDE_RIGHT = 0x400, + SPEAKER_TOP_CENTER = 0x800, + SPEAKER_TOP_FRONT_LEFT = 0x1000, + SPEAKER_TOP_FRONT_CENTER = 0x2000, + SPEAKER_TOP_FRONT_RIGHT = 0x4000, + SPEAKER_TOP_BACK_LEFT = 0x8000, + SPEAKER_TOP_BACK_CENTER = 0x10000, + SPEAKER_TOP_BACK_RIGHT = 0x20000, + + DIRECTOUT = 0, + KSAUDIO_SPEAKER_MONO = (SPEAKER_FRONT_CENTER), + KSAUDIO_SPEAKER_STEREO = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT), + KSAUDIO_SPEAKER_QUAD = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + KSAUDIO_SPEAKER_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER), + KSAUDIO_SPEAKER_5POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), + KSAUDIO_SPEAKER_5POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + KSAUDIO_SPEAKER_7POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER), + KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + + DVDAUDIO_GR1_0 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_2 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_3 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_4 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_5 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_6 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_7 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_8 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_9 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_10 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_11 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_12 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_13 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_14 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_15 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_16 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_17 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_18 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_19 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_20 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + + DVDAUDIO_GR2_0 = 0, + DVDAUDIO_GR2_1 = 0, + DVDAUDIO_GR2_2 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_3 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_4 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_5 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_6 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_7 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_8 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_9 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_10 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_11 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_12 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_13 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_14 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_15 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_16 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_17 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_18 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_19 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_20 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, + } + + private int _bitsPerSample; + private int _channelCount; + private int _sampleRate; + private SpeakerConfig _channelMask; + + public int BitsPerSample { get { return _bitsPerSample; } } + public int ChannelCount { get { return _channelCount; } } + public int SampleRate { get { return _sampleRate; } } + public int BlockAlign { get { return _channelCount * ((_bitsPerSample + 7) / 8); } } + public SpeakerConfig ChannelMask { get { return _channelMask; } } + public bool IsRedBook { get { return _bitsPerSample == 16 && _channelCount == 2 && _sampleRate == 44100; } } + public static int ChannelsInMask(SpeakerConfig mask) + { + int count = 0; + while (mask != 0) + { + count++; + mask &= (mask - 1); + } + return count; + } + + public static SpeakerConfig GetDefaultChannelMask(int channelCount) + { + switch (channelCount) + { + case 1: + return SpeakerConfig.KSAUDIO_SPEAKER_MONO; + case 2: + return SpeakerConfig.KSAUDIO_SPEAKER_STEREO; + case 3: + return SpeakerConfig.KSAUDIO_SPEAKER_STEREO | SpeakerConfig.SPEAKER_LOW_FREQUENCY; + case 4: + return SpeakerConfig.KSAUDIO_SPEAKER_QUAD; + case 5: + //return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1 & ~SpeakerConfig.SPEAKER_LOW_FREQUENCY; + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND & ~SpeakerConfig.SPEAKER_LOW_FREQUENCY; + case 6: + //return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1; + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND; + case 7: + return SpeakerConfig.KSAUDIO_SPEAKER_5POINT1_SURROUND | SpeakerConfig.SPEAKER_BACK_CENTER; + case 8: + return SpeakerConfig.KSAUDIO_SPEAKER_7POINT1_SURROUND; + } + return (SpeakerConfig)((1 << channelCount) - 1); + } + + public AudioPCMConfig(int bitsPerSample, int channelCount, int sampleRate, SpeakerConfig channelMask = SpeakerConfig.DIRECTOUT) + { + _bitsPerSample = bitsPerSample; + _channelCount = channelCount; + _sampleRate = sampleRate; + _channelMask = channelMask == 0 ? GetDefaultChannelMask(channelCount) : channelMask; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPipe.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPipe.cs new file mode 100644 index 000000000..5715d8017 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioPipe.cs @@ -0,0 +1,269 @@ +using System; +using System.Threading; + +namespace CUETools.Codecs +{ + public class AudioPipe : IAudioSource//, IDisposable + { + private AudioBuffer _readBuffer, _writeBuffer; + private AudioPCMConfig pcm; + private long _sampleLen, _samplePos; + private int _maxLength; + private Thread _workThread; + private IAudioSource _source; + private bool _close = false; + private bool _haveData = false; + private int _bufferPos = 0; + private Exception _ex = null; + private bool own; + private ThreadPriority priority; + + public IAudioDecoderSettings Settings => null; + + public long Position + { + get + { + return _samplePos; + } + set + { + if (value == _samplePos) + return; + + if (_source == null) + throw new NotSupportedException(); + + lock (this) + { + _close = true; + Monitor.Pulse(this); + } + if (_workThread != null) + { + _workThread.Join(); + _workThread = null; + } + _source.Position = value; + _samplePos = value; + _bufferPos = 0; + _haveData = false; + _close = false; + //Go(); + //throw new Exception("not supported"); + } + } + + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length + { + get + { + return _sampleLen; + } + } + + public long Remaining + { + get + { + return _sampleLen - _samplePos; + } + } + + public AudioPCMConfig PCM + { + get + { + return pcm; + } + } + + public string Path + { + get + { + if (_source == null) + return ""; + return _source.Path; + } + } + + public AudioPipe(AudioPCMConfig pcm, int size) + { + this.pcm = pcm; + _readBuffer = new AudioBuffer(pcm, size); + _writeBuffer = new AudioBuffer(pcm, size); + _maxLength = size; + _sampleLen = -1; + _samplePos = 0; + } + + public AudioPipe(IAudioSource source, int size, bool own, ThreadPriority priority) + : this(source.PCM, size) + { + this.own = own; + this.priority = priority; + _source = source; + _sampleLen = _source.Length; + _samplePos = _source.Position; + } + + public AudioPipe(IAudioSource source, int size) + : this(source, size, true, ThreadPriority.BelowNormal) + { + } + + private void Decompress(object o) + { +#if !DEBUG + try +#endif + { + bool done = false; + do + { + done = _source.Read(_writeBuffer, -1) == 0; + lock (this) + { + while (_haveData && !_close) + Monitor.Wait(this); + if (_close) + break; + AudioBuffer temp = _writeBuffer; + _writeBuffer = _readBuffer; + _readBuffer = temp; + _haveData = true; + Monitor.Pulse(this); + } + } while (!done); + } +#if !DEBUG + catch (Exception ex) + { + lock (this) + { + _ex = ex; + Monitor.Pulse(this); + } + } +#endif + } + + private void Go() + { + if (_workThread != null || _ex != null || _source == null) return; + _workThread = new Thread(Decompress); + _workThread.Priority = priority; + _workThread.IsBackground = true; + _workThread.Name = "AudioPipe"; + _workThread.Start(null); + } + + //public new void Dispose() + //{ + // _buffer.Clear(); + //} + + public void Close() + { + lock (this) + { + _close = true; + Monitor.Pulse(this); + } + if (_workThread != null) + { + _workThread.Join(); + _workThread = null; + } + if (_source != null) + { + if (own) _source.Close(); + _source = null; + } + if (_readBuffer != null) + { + //_readBuffer.Clear(); + _readBuffer = null; + } + if (_writeBuffer != null) + { + //_writeBuffer.Clear(); + _writeBuffer = null; + } + } + + public int Write(AudioBuffer buff) + { + if (_writeBuffer.Size < _writeBuffer.Length + buff.Length) + { + AudioBuffer realloced = new AudioBuffer(pcm, _writeBuffer.Size + buff.Size); + realloced.Prepare(_writeBuffer, 0, _writeBuffer.Length); + _writeBuffer = realloced; + } + if (_writeBuffer.Length == 0) + _writeBuffer.Prepare(buff, 0, buff.Length); + else + { + _writeBuffer.Load(_writeBuffer.Length, buff, 0, buff.Length); + _writeBuffer.Length += buff.Length; + } + lock (this) + { + if (!_haveData) + { + AudioBuffer temp = _writeBuffer; + _writeBuffer = _readBuffer; + _writeBuffer.Length = 0; + _readBuffer = temp; + _haveData = true; + Monitor.Pulse(this); + } + } + return _writeBuffer.Length; + } + + public int Read(AudioBuffer buff, int maxLength) + { + Go(); + + bool needToCopy = false; + if (_bufferPos != 0) + needToCopy = true; + else + lock (this) + { + while (!_haveData && _ex == null) + Monitor.Wait(this); + if (_ex != null) + throw _ex; + if (_bufferPos == 0 && (maxLength < 0 || _readBuffer.Length <= maxLength)) + { + buff.Swap(_readBuffer); + _haveData = false; + Monitor.Pulse(this); + } + else + needToCopy = true; + } + if (needToCopy) + { + buff.Prepare(_readBuffer, _bufferPos, maxLength); + _bufferPos += buff.Length; + if (_bufferPos == _readBuffer.Length) + { + _bufferPos = 0; + lock (this) + { + _haveData = false; + Monitor.Pulse(this); + } + } + } + _samplePos += buff.Length; + return buff.Length; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioSamples.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioSamples.cs new file mode 100644 index 000000000..a8bf2c54e --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/AudioSamples.cs @@ -0,0 +1,142 @@ +using System; + +namespace CUETools.Codecs +{ + public class AudioSamples + { + public const uint UINT32_MAX = 0xffffffff; + + unsafe public static void Interlace(int* res, int* src1, int* src2, int n) + { + for (int i = n; i > 0; i--) + { + *(res++) = *(src1++); + *(res++) = *(src2++); + } + } + + unsafe public static void Deinterlace(int* dst1, int* dst2, int* src, int n) + { + for (int i = n; i > 0; i--) + { + *(dst1++) = *(src++); + *(dst2++) = *(src++); + } + } + + unsafe public static bool MemCmp(int* res, int* smp, int n) + { + for (int i = n; i > 0; i--) + if (*(res++) != *(smp++)) + return true; + return false; + } + + unsafe public static void MemCpy(uint* res, uint* smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + + unsafe public static void MemCpy(int* res, int* smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + + unsafe public static void MemCpy(long* res, long* smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + + unsafe public static void MemCpy(short* res, short* smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + + unsafe public static void MemCpy(byte* res, byte* smp, int n) + { + if ((((IntPtr)smp).ToInt64() & 7) == (((IntPtr)res).ToInt64() & 7) && n > 32) + { + int delta = (int)((8 - (((IntPtr)smp).ToInt64() & 7)) & 7); + for (int i = delta; i > 0; i--) + *(res++) = *(smp++); + n -= delta; + + MemCpy((long*)res, (long*)smp, n >> 3); + int n8 = (n >> 3) << 3; + n -= n8; + smp += n8; + res += n8; + } + if ((((IntPtr)smp).ToInt64() & 3) == (((IntPtr)res).ToInt64() & 3) && n > 16) + { + int delta = (int)((4 - (((IntPtr)smp).ToInt64() & 3)) & 3); + for (int i = delta; i > 0; i--) + *(res++) = *(smp++); + n -= delta; + + MemCpy((int*)res, (int*)smp, n >> 2); + int n4 = (n >> 2) << 2; + n -= n4; + smp += n4; + res += n4; + } + for (int i = n; i > 0; i--) + *(res++) = *(smp++); + } + + unsafe public static void MemSet(int* res, int smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = smp; + } + + unsafe public static void MemSet(long* res, long smp, int n) + { + for (int i = n; i > 0; i--) + *(res++) = smp; + } + + unsafe public static void MemSet(byte* res, byte smp, int n) + { + if (IntPtr.Size == 8 && (((IntPtr)res).ToInt64() & 7) == 0 && smp == 0 && n > 8) + { + MemSet((long*)res, 0, n >> 3); + int n8 = (n >> 3) << 3; + n -= n8; + res += n8; + } + if ((((IntPtr)res).ToInt64() & 3) == 0 && smp == 0 && n > 4) + { + MemSet((int*)res, 0, n >> 2); + int n4 = (n >> 2) << 2; + n -= n4; + res += n4; + } + for (int i = n; i > 0; i--) + *(res++) = smp; + } + + unsafe public static void MemSet(byte[] res, byte smp, int offs, int n) + { + fixed (byte* pres = &res[offs]) + MemSet(pres, smp, n); + } + + unsafe public static void MemSet(int[] res, int smp, int offs, int n) + { + fixed (int* pres = &res[offs]) + MemSet(pres, smp, n); + } + + unsafe public static void MemSet(long[] res, long smp, int offs, int n) + { + fixed (long* pres = &res[offs]) + MemSet(pres, smp, n); + } + } + +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/BitReader.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/BitReader.cs new file mode 100644 index 000000000..d2960607b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/BitReader.cs @@ -0,0 +1,335 @@ +using System; + +namespace CUETools.Codecs +{ + unsafe public class BitReader + { + #region Static Methods + + public static int log2i(int v) + { + return log2i((uint)v); + } + + public static readonly byte[] MultiplyDeBruijnBitPosition = new byte[32] + { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31 + }; + + public static int log2i(ulong v) + { + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + if (v >> 32 == 0) + return MultiplyDeBruijnBitPosition[(uint)((uint)v * 0x07C4ACDDU) >> 27]; + return 32 + MultiplyDeBruijnBitPosition[(uint)((uint)(v >> 32) * 0x07C4ACDDU) >> 27]; + } + + public static int log2i(uint v) + { + v |= v >> 1; // first round down to one less than a power of 2 + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return MultiplyDeBruijnBitPosition[(uint)(v * 0x07C4ACDDU) >> 27]; + } + + public static readonly byte[] byte_to_unary_table = new byte[] + { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + #endregion + + private byte* buffer_m; + private byte* bptr_m; + private int buffer_len_m; + private int have_bits_m; + private ulong cache_m; + private ushort crc16_m; + + public int Position + { + get { return (int)(bptr_m - buffer_m - (have_bits_m >> 3)); } + } + + public byte* Buffer + { + get + { + return buffer_m; + } + } + + public BitReader() + { + buffer_m = null; + bptr_m = null; + buffer_len_m = 0; + have_bits_m = 0; + cache_m = 0; + crc16_m = 0; + } + + public BitReader(byte* _buffer, int _pos, int _len) + { + Reset(_buffer, _pos, _len); + } + + public void Reset(byte* _buffer, int _pos, int _len) + { + buffer_m = _buffer; + bptr_m = _buffer + _pos; + buffer_len_m = _len; + have_bits_m = 0; + cache_m = 0; + crc16_m = 0; + fill(); + } + + public void fill() + { + while (have_bits_m < 56) + { + have_bits_m += 8; + byte b = *(bptr_m++); + cache_m |= (ulong)b << (64 - have_bits_m); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ b]); + } + } + + /* skip any number of bits */ + public void skipbits(int bits) + { + while (bits > have_bits_m) + { + bits -= have_bits_m; + cache_m = 0; + have_bits_m = 0; + fill(); + } + cache_m <<= bits; + have_bits_m -= bits; + } + + public long read_long() + { + return ((long)readbits(32) << 32) | readbits(32); + } + + public ulong read_ulong() + { + return ((ulong)readbits(32) << 32) | readbits(32); + } + + public int read_int() + { + return (int)readbits(sizeof(int)); + } + + public uint read_uint() + { + return (uint)readbits(sizeof(uint)); + } + + public short read_short() + { + return (short)readbits(16); + } + + public ushort read_ushort() + { + return (ushort)readbits(16); + } + + /* supports reading 1 to 32 bits, in big endian format */ + public uint readbits(int bits) + { + fill(); + uint result = (uint)(cache_m >> 64 - bits); + skipbits(bits); + return result; + } + + /* supports reading 1 to 64 bits, in big endian format */ + public ulong readbits64(int bits) + { + if (bits <= 56) + return readbits(bits); + return ((ulong)readbits(32) << bits - 32) | readbits(bits - 32); + } + + /* reads a single bit */ + public uint readbit() + { + return readbits(1); + } + + public uint read_unary() + { + fill(); + uint val = 0; + ulong result = cache_m >> 56; + while (result == 0) + { + val += 8; + cache_m <<= 8; + byte b = *(bptr_m++); + cache_m |= (ulong)b << (64 - have_bits_m); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ b]); + result = cache_m >> 56; + } + val += byte_to_unary_table[result]; + skipbits((int)(val & 7) + 1); + return val; + } + + public void flush() + { + if ((have_bits_m & 7) > 0) + { + cache_m <<= have_bits_m & 7; + have_bits_m -= have_bits_m & 7; + } + } + + public ushort get_crc16() + { + if (have_bits_m == 0) + return crc16_m; + ushort crc = 0; + int n = have_bits_m >> 3; + for (int i = 0; i < n; i++) + crc = (ushort)((crc << 8) ^ Crc16.table[(crc >> 8) ^ (byte)(cache_m >> (56 - (i << 3)))]); + return Crc16.Subtract(crc16_m, crc, n); + } + + public int readbits_signed(int bits) + { + int val = (int)readbits(bits); + val <<= (32 - bits); + val >>= (32 - bits); + return val; + } + + public uint read_utf8() + { + uint x = readbits(8); + uint v; + int i; + if (0 == (x & 0x80)) + { + v = x; + i = 0; + } + else if (0xC0 == (x & 0xE0)) /* 110xxxxx */ + { + v = x & 0x1F; + i = 1; + } + else if (0xE0 == (x & 0xF0)) /* 1110xxxx */ + { + v = x & 0x0F; + i = 2; + } + else if (0xF0 == (x & 0xF8)) /* 11110xxx */ + { + v = x & 0x07; + i = 3; + } + else if (0xF8 == (x & 0xFC)) /* 111110xx */ + { + v = x & 0x03; + i = 4; + } + else if (0xFC == (x & 0xFE)) /* 1111110x */ + { + v = x & 0x01; + i = 5; + } + else if (0xFE == x) /* 11111110 */ + { + v = 0; + i = 6; + } + else + { + throw new Exception("invalid utf8 encoding"); + } + for (; i > 0; i--) + { + x = readbits(8); + if (0x80 != (x & 0xC0)) /* 10xxxxxx */ + throw new Exception("invalid utf8 encoding"); + v <<= 6; + v |= (x & 0x3F); + } + return v; + } + + public void read_rice_block(int n, int k, int* r) + { + fill(); + fixed (byte* unary_table = byte_to_unary_table) + fixed (ushort* t = Crc16.table) + { + uint mask = (1U << k) - 1; + byte* bptr = bptr_m; + int have_bits = have_bits_m; + ulong cache = cache_m; + ushort crc = crc16_m; + for (int i = n; i > 0; i--) + { + uint bits; + byte* orig_bptr = bptr; + while ((bits = unary_table[cache >> 56]) == 8) + { + cache <<= 8; + byte b = *(bptr++); + cache |= (ulong)b << (64 - have_bits); + crc = (ushort)((crc << 8) ^ t[(crc >> 8) ^ b]); + } + uint msbs = bits + ((uint)(bptr - orig_bptr) << 3); + // assumes k <= 41 (have_bits < 41 + 7 + 1 + 8 == 57, so we don't loose bits here) + while (have_bits < 56) + { + have_bits += 8; + byte b = *(bptr++); + cache |= (ulong)b << (64 - have_bits); + crc = (ushort)((crc << 8) ^ t[(crc >> 8) ^ b]); + } + + int btsk = k + (int)bits + 1; + uint uval = (msbs << k) | (uint)((cache >> (64 - btsk)) & mask); + cache <<= btsk; + have_bits -= btsk; + *(r++) = (int)(uval >> 1 ^ -(int)(uval & 1)); + } + have_bits_m = have_bits; + cache_m = cache; + bptr_m = bptr; + crc16_m = crc; + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/BitWriter.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/BitWriter.cs new file mode 100644 index 000000000..524c727ac --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/BitWriter.cs @@ -0,0 +1,370 @@ +using System; + +namespace CUETools.Codecs +{ + public class BitWriter + { + private ushort crc16_m; + private ulong bit_buf_m; + private int bit_left_m; + private byte[] buffer; + private int buf_start, buf_ptr_m, buf_end; + private bool eof; + + public byte[] Buffer + { + get + { + return buffer; + } + } + + public int Length + { + get + { + return buf_ptr_m - buf_start; + } + set + { + flush(); + buf_ptr_m = buf_start + value; + } + } + + public int BitLength + { + get + { + return buf_ptr_m * 8 + 64 - bit_left_m; + } + } + + public ushort get_crc16() + { + return crc16_m; + } + + public BitWriter(byte[] buf, int pos, int len) + { + buffer = buf; + buf_start = pos; + buf_ptr_m = pos; + buf_end = pos + len; + bit_left_m = 64; + bit_buf_m = 0; + crc16_m = 0; + eof = false; + } + + public void Reset() + { + buf_ptr_m = buf_start; + bit_left_m = 64; + bit_buf_m = 0; + crc16_m = 0; + eof = false; + } + + public void writebytes(int bytes, byte c) + { + for (; bytes > 0; bytes--) + { + writebits(8, c); + } + } + + public unsafe void writeints(int len, int pos, byte* buf) + { + int old_pos = BitLength; + int start = old_pos / 8; + int start1 = pos / 8; + int end = (old_pos + len) / 8; + int end1 = (pos + len) / 8; + flush(); + byte start_val = old_pos % 8 != 0 ? buffer[start] : (byte)0; + fixed (byte* buf1 = &buffer[0]) + { + if (old_pos % 8 != 0) + crc16_m = Crc16.Subtract(crc16_m, 0, 1); + crc16_m = Crc16.ComputeChecksum(crc16_m, buf + start1, end - start); + AudioSamples.MemCpy(buf1 + start, buf + start1, end - start); + buf1[start] |= start_val; + } + buf_ptr_m = end; + if ((old_pos + len) % 8 != 0) + writebits((old_pos + len) % 8, buf[end1] >> (8 - ((old_pos + len) % 8))); + } + + public void write(params char[] chars) + { + foreach (char c in chars) + writebits(8, (byte)c); + } + + public void write(string s) + { + for (int i = 0; i < s.Length; i++) + writebits(8, (byte)s[i]); + } + + public void write(byte[] s) + { + for (int i = 0; i < s.Length; i++) + writebits(8, s[i]); + } + + public void writebits_signed(int bits, int val) + { + writebits(bits, val & ((1 << bits) - 1)); + } + + public void writebits_signed(uint bits, int val) + { + writebits((int)bits, val & ((1 << (int)bits) - 1)); + } + + public void writebits(int bits, int val) + { + writebits(bits, (ulong)val); + } + + public void writebits(DateTime val) + { + TimeSpan span = val.ToUniversalTime() - new DateTime(1904, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + writebits(32, (ulong)span.TotalSeconds); + } + + public void writebits(int bits, uint val) + { + writebits(bits, (ulong)val); + } + + public void writebits(int bits, ulong val) + { + //assert(bits == 32 || val < (1U << bits)); + + if (bits == 0 || eof) return; + if (bits < bit_left_m) + { + bit_left_m -= bits; + bit_buf_m |= val << bit_left_m; + } + else + { + ulong bb = bit_buf_m | (val >> (bits - bit_left_m)); + if (buffer != null) + { + if (buf_ptr_m + 8 > buf_end) + { + eof = true; + return; + } + + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 56)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 48)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 40)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 32)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 24)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 16)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb >> 8)]); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ (byte)(bb )]); + + buffer[buf_ptr_m + 7] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 6] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 5] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 4] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 3] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 2] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 1] = (byte)(bb & 0xFF); bb >>= 8; + buffer[buf_ptr_m + 0] = (byte)(bb & 0xFF); + buf_ptr_m += 8; + } + // cannot do this in one shift, because bit_left_m can be 64, + // + bit_left_m += 64 - bits; + bit_buf_m = bit_left_m == 64 ? 0 : val << bit_left_m; + } + } + + /// + /// Assumes there's enough space, buffer != null and bits is in range 1..31 + /// + /// + /// +// unsafe void writebits_fast(int bits, uint val, ref byte* buf) +// { +//#if DEBUG +// if ((buf_ptr + 3) >= buf_end) +// { +// eof = true; +// return; +// } +//#endif +// if (bits < bit_left) +// { +// bit_buf = (bit_buf << bits) | val; +// bit_left -= bits; +// } +// else +// { +// uint bb = (bit_buf << bit_left) | (val >> (bits - bit_left)); +// bit_left += (32 - bits); + +// *(buf++) = (byte)(bb >> 24); +// *(buf++) = (byte)(bb >> 16); +// *(buf++) = (byte)(bb >> 8); +// *(buf++) = (byte)(bb); + +// bit_buf = val; +// } +// } + + public void write_utf8(int val) + { + write_utf8((uint)val); + } + + public void write_utf8(uint val) + { + if (val < 0x80) + { + writebits(8, val); + return; + } + int bytes = (BitReader.log2i(val) + 4) / 5; + int shift = (bytes - 1) * 6; + writebits(8, (256U - (256U >> bytes)) | (val >> shift)); + while (shift >= 6) + { + shift -= 6; + writebits(8, 0x80 | ((val >> shift) & 0x3F)); + } + } + + public void write_unary_signed(int val) + { + // convert signed to unsigned + int v = -2 * val - 1; + v ^= (v >> 31); + + // write quotient in unary + int q = v + 1; + while (q > 31) + { + writebits(31, 0); + q -= 31; + } + writebits(q, 1); + } + + public void write_rice_signed(int k, int val) + { + // convert signed to unsigned + int v = -2 * val - 1; + v ^= (v >> 31); + + // write quotient in unary + int q = (v >> k) + 1; + while (q + k > 31) + { + int b = Math.Min(q + k - 31, 31); + writebits(b, 0); + q -= b; + } + + // write remainder in binary using 'k' bits + writebits(k + q, (v & ((1 << k) - 1)) | (1 << k)); + } + + public unsafe void write_rice_block_signed(byte* fixedbuf, int k, int* residual, int count) + { + byte* buf = &fixedbuf[buf_ptr_m]; + ulong bit_buf = bit_buf_m; + int bit_left = bit_left_m; + ushort crc16 = crc16_m; + fixed (ushort *crc16_t = Crc16.table) + for (int i = count; i > 0; i--) + { + int vi = *(residual++); + uint v = (uint)((vi << 1) ^ (vi >> 31)); + + // write quotient in unary + int q = (int)(v >> k) + 1; + int bits = k + q; + while (bits > 64) + { +#if DEBUG + if (buf + 1 > fixedbuf + buf_end) + { + eof = true; + return; + } +#endif + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bit_buf >> 56))]); + bit_buf <<= 8; + bits -= 8; + } + + // write remainder in binary using 'k' bits + //writebits_fast(k + q, (uint)((v & ((1 << k) - 1)) | (1 << k)), ref buf); + ulong val = (uint)((v & ((1U << k) - 1)) | (1U << k)); + if (bits < bit_left) + { + bit_left -= bits; + bit_buf |= val << bit_left; + } + else + { + ulong bb = bit_buf | (val >> (bits - bit_left)); +#if DEBUG + if (buf + 8 > fixedbuf + buf_end) + { + eof = true; + return; + } +#endif + + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 56))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 48))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 40))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 32))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 24))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 16))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb >> 8))]); + crc16 = (ushort)((crc16 << 8) ^ crc16_t[(crc16 >> 8) ^ (*(buf++) = (byte)(bb))]); + + bit_left += 64 - bits; + bit_buf = (val << bit_left - 1) << 1; + } + } + crc16_m = crc16; + buf_ptr_m = (int)(buf - fixedbuf); + bit_buf_m = bit_buf; + bit_left_m = bit_left; + } + + public void flush() + { + while (bit_left_m < 64 && !eof) + { + if (buf_ptr_m >= buf_end) + { + eof = true; + break; + } + if (buffer != null) + { + byte b = (byte)(bit_buf_m >> 56); + crc16_m = (ushort)((crc16_m << 8) ^ Crc16.table[(crc16_m >> 8) ^ b]); + buffer[buf_ptr_m] = b; + } + buf_ptr_m++; + bit_buf_m <<= 8; + bit_left_m += 8; + } + bit_left_m = 64; + bit_buf_m = 0; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16.cs new file mode 100644 index 000000000..85b321919 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16.cs @@ -0,0 +1,164 @@ +using System; +namespace CUETools.Codecs +{ + public static class Crc16 + { + const int GF2_DIM = 16; + public static ushort[] table = new ushort[256]; + private static readonly ushort[,] combineTable = new ushort[GF2_DIM, GF2_DIM]; + private static readonly ushort[,] substractTable = new ushort[GF2_DIM, GF2_DIM]; + + public static unsafe ushort ComputeChecksum(ushort crc, byte[] bytes, int pos, int count) + { + fixed (byte* bs = bytes) + return ComputeChecksum(crc, bs + pos, count); + } + + public static unsafe ushort ComputeChecksum(ushort crc, byte* bytes, int count) + { + fixed (ushort* t = table) + for (int i = count; i > 0; i--) + { + crc = (ushort)((crc << 8) ^ t[(crc >> 8) ^ *(bytes++)]); + } + return crc; + } + + const ushort polynomial = 0x8005; + const ushort reversePolynomial = 0x4003; + + static unsafe Crc16() + { + for (ushort i = 0; i < table.Length; i++) + { + int crc = i; + for (int j = 0; j < GF2_DIM; j++) + { + if ((crc & (1U << (GF2_DIM - 1))) != 0) + crc = ((crc << 1) ^ polynomial); + else + crc <<= 1; + } + table[i] = (ushort)(crc & ((1 << GF2_DIM) - 1)); + } + + combineTable[0, 0] = Crc16.Reflect(polynomial); + substractTable[0, GF2_DIM - 1] = reversePolynomial; + for (int n = 1; n < GF2_DIM; n++) + { + combineTable[0, n] = (ushort)(1 << (n - 1)); + substractTable[0, n - 1] = (ushort)(1 << n); + } + + fixed (ushort* ct = &combineTable[0, 0], st = &substractTable[0, 0]) + { + //for (int i = 0; i < GF2_DIM; i++) + // st[32 + i] = ct[i]; + //invert_binary_matrix(st + 32, st, GF2_DIM); + + for (int i = 1; i < GF2_DIM; i++) + { + gf2_matrix_square(ct + i * GF2_DIM, ct + (i - 1) * GF2_DIM); + gf2_matrix_square(st + i * GF2_DIM, st + (i - 1) * GF2_DIM); + } + } + } + + private static unsafe ushort gf2_matrix_times(ushort* mat, ushort uvec) + { + int vec = ((int) uvec) << 16; + return (ushort)( + (*(mat++) & ((vec << 15) >> 31)) ^ + (*(mat++) & ((vec << 14) >> 31)) ^ + (*(mat++) & ((vec << 13) >> 31)) ^ + (*(mat++) & ((vec << 12) >> 31)) ^ + (*(mat++) & ((vec << 11) >> 31)) ^ + (*(mat++) & ((vec << 10) >> 31)) ^ + (*(mat++) & ((vec << 09) >> 31)) ^ + (*(mat++) & ((vec << 08) >> 31)) ^ + (*(mat++) & ((vec << 07) >> 31)) ^ + (*(mat++) & ((vec << 06) >> 31)) ^ + (*(mat++) & ((vec << 05) >> 31)) ^ + (*(mat++) & ((vec << 04) >> 31)) ^ + (*(mat++) & ((vec << 03) >> 31)) ^ + (*(mat++) & ((vec << 02) >> 31)) ^ + (*(mat++) & ((vec << 01) >> 31)) ^ + (*(mat++) & (vec >> 31))); + } + + private static unsafe void gf2_matrix_square(ushort* square, ushort* mat) + { + for (int n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); + } + + public static ushort Reflect(ushort crc) + { + return (ushort)Crc32.Reflect(crc, 16); + } + + public static unsafe ushort Combine(ushort crc1, ushort crc2, long len2) + { + crc1 = Crc16.Reflect(crc1); + crc2 = Crc16.Reflect(crc2); + + /* degenerate case */ + if (len2 == 0) + return crc1; + if (crc1 == 0) + return crc2; + if (len2 < 0) + throw new ArgumentException("crc.Combine length cannot be negative", "len2"); + + fixed (ushort* ct = &combineTable[0, 0]) + { + int n = 3; + do + { + /* apply zeros operator for this bit of len2 */ + if ((len2 & 1) != 0) + crc1 = gf2_matrix_times(ct + GF2_DIM * n, crc1); + len2 >>= 1; + n = (n + 1) & (GF2_DIM - 1); + /* if no more bits set, then done */ + } while (len2 != 0); + } + + /* return combined crc */ + crc1 ^= crc2; + crc1 = Crc16.Reflect(crc1); + return crc1; + } + + public static unsafe ushort Subtract(ushort crc1, ushort crc2, long len2) + { + crc1 = Crc16.Reflect(crc1); + crc2 = Crc16.Reflect(crc2); + /* degenerate case */ + if (len2 == 0) + return crc1; + if (len2 < 0) + throw new ArgumentException("crc.Combine length cannot be negative", "len2"); + + crc1 ^= crc2; + + fixed (ushort* st = &substractTable[0, 0]) + { + int n = 3; + do + { + /* apply zeros operator for this bit of len2 */ + if ((len2 & 1) != 0) + crc1 = gf2_matrix_times(st + GF2_DIM * n, crc1); + len2 >>= 1; + n = (n + 1) & (GF2_DIM - 1); + /* if no more bits set, then done */ + } while (len2 != 0); + } + + /* return combined crc */ + crc1 = Crc16.Reflect(crc1); + return crc1; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16CCITT.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16CCITT.cs new file mode 100644 index 000000000..d38d8821b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC16CCITT.cs @@ -0,0 +1,51 @@ +namespace CUETools.Codecs +{ + public enum InitialCrcValue { Zeros, NonZero1 = 0xffff, NonZero2 = 0x1D0F } + + public class Crc16Ccitt + { + const ushort poly = 4129; + ushort[] table = new ushort[256]; + ushort initialValue = 0; + + public ushort ComputeChecksum(byte[] bytes, int pos, int count) + { + ushort crc = this.initialValue; + for (int i = pos; i < pos + count; i++) + { + crc = (ushort)((crc << 8) ^ table[((crc >> 8) ^ (0xff & bytes[i]))]); + } + return crc; + } + + public byte[] ComputeChecksumBytes(byte[] bytes, int pos, int count) + { + ushort crc = ComputeChecksum(bytes, pos, count); + return new byte[] { (byte)(crc >> 8), (byte)(crc & 0x00ff) }; + } + + public Crc16Ccitt(InitialCrcValue initialValue) + { + this.initialValue = (ushort)initialValue; + ushort temp, a; + for (int i = 0; i < table.Length; i++) + { + temp = 0; + a = (ushort)(i << 8); + for (int j = 0; j < 8; j++) + { + if (((temp ^ a) & 0x8000) != 0) + { + temp = (ushort)((temp << 1) ^ poly); + } + else + { + temp <<= 1; + } + a <<= 1; + } + table[i] = temp; + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC32.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC32.cs new file mode 100644 index 000000000..48a629f39 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC32.cs @@ -0,0 +1,259 @@ +using System; + +namespace CUETools.Codecs +{ + public static class Crc32 + { + public static readonly uint[] table; + + public static uint ComputeChecksum(uint crc, byte val) + { + return (crc >> 8) ^ table[(crc & 0xff) ^ val]; + } + + public static unsafe uint ComputeChecksum(uint crc, byte* bytes, int count) + { + fixed (uint *t = table) + for (int i = 0; i < count; i++) + crc = (crc >> 8) ^ t[(crc ^ bytes[i]) & 0xff]; + return crc; + } + + public static unsafe uint ComputeChecksum(uint crc, byte[] bytes, int pos, int count) + { + fixed (byte* pbytes = &bytes[pos]) + return ComputeChecksum(crc, pbytes, count); + } + + public static uint ComputeChecksum(uint crc, uint s) + { + return ComputeChecksum(ComputeChecksum(ComputeChecksum(ComputeChecksum( + crc, (byte)s), (byte)(s >> 8)), (byte)(s >> 16)), (byte)(s >> 24)); + } + + public static unsafe uint ComputeChecksum(uint crc, int* samples, int count) + { + for (int i = 0; i < count; i++) + { + int s1 = samples[2 * i], s2 = samples[2 * i + 1]; + crc = ComputeChecksum(ComputeChecksum(ComputeChecksum(ComputeChecksum( + crc, (byte)s1), (byte)(s1 >> 8)), (byte)s2), (byte)(s2 >> 8)); + } + return crc; + } + + internal static uint Reflect(uint val, int ch) + { + uint value = 0; + // Swap bit 0 for bit 7 + // bit 1 for bit 6, etc. + for (int i = 1; i < (ch + 1); i++) + { + if (0 != (val & 1)) + value |= 1U << (ch - i); + val >>= 1; + } + return value; + } + + const uint uPolynomial = 0x04c11db7; + const uint uReversePolynomial = 0xedb88320; + const uint uReversePolynomial2 = 0xdb710641; + + private static readonly uint[,] combineTable; + private static readonly uint[,] substractTable; + +#if need_invert_binary_matrix + static unsafe void invert_binary_matrix(uint* mat, uint* inv, int rows) + { + int cols, i, j; + uint tmp; + + cols = rows; + + for (i = 0; i < rows; i++) inv[i] = (1U << i); + + /* First -- convert into upper triangular */ + + for (i = 0; i < cols; i++) + { + + /* Swap rows if we ave a zero i,i element. If we can't swap, then the + matrix was not invertible */ + + if ((mat[i] & (1 << i)) == 0) + { + for (j = i + 1; j < rows && (mat[j] & (1 << i)) == 0; j++) ; + if (j == rows) + throw new Exception("Matrix not invertible"); + tmp = mat[i]; mat[i] = mat[j]; mat[j] = tmp; + tmp = inv[i]; inv[i] = inv[j]; inv[j] = tmp; + } + + /* Now for each j>i, add A_ji*Ai to Aj */ + for (j = i + 1; j != rows; j++) + { + if ((mat[j] & (1 << i)) != 0) + { + mat[j] ^= mat[i]; + inv[j] ^= inv[i]; + } + } + } + + /* Now the matrix is upper triangular. Start at the top and multiply down */ + + for (i = rows - 1; i >= 0; i--) + { + for (j = 0; j < i; j++) + { + if ((mat[j] & (1 << i)) != 0) + { + /* mat[j] ^= mat[i]; */ + inv[j] ^= inv[i]; + } + } + } + } +#endif + + static unsafe Crc32() + { + table = new uint[256]; + for (uint i = 0; i < table.Length; i++) + { + table[i] = Reflect(i, 8) << 24; + for (int j = 0; j < 8; j++) + table[i] = (table[i] << 1) ^ ((table[i] & (1U << 31)) == 0 ? 0 : uPolynomial); + table[i] = Reflect(table[i], 32); + } + combineTable = new uint[GF2_DIM, GF2_DIM]; + substractTable = new uint[GF2_DIM, GF2_DIM]; + combineTable[0, 0] = uReversePolynomial; + substractTable[0, 31] = uReversePolynomial2; + for (int n = 1; n < GF2_DIM; n++) + { + combineTable[0, n] = 1U << (n - 1); + substractTable[0, n - 1] = 1U << n; + } + fixed (uint* ct = &combineTable[0, 0], st = &substractTable[0, 0]) + { + //for (int i = 0; i < GF2_DIM; i++) + // st[32 + i] = ct[i]; + //invert_binary_matrix(st + 32, st, GF2_DIM); + + for (int i = 1; i < GF2_DIM; i++) + { + gf2_matrix_square(ct + i * 32, ct + (i - 1) * 32); + gf2_matrix_square(st + i * 32, st + (i - 1) * 32); + } + } + } + + const int GF2_DIM = 32; + //const int GF2_DIM2 = 67; + + private static unsafe uint gf2_matrix_times(uint* umat, uint uvec) + { + int vec = (int)uvec; + int* mat = (int*)umat; + return (uint)( + (*(mat++) & ((vec << 31) >> 31)) ^ + (*(mat++) & ((vec << 30) >> 31)) ^ + (*(mat++) & ((vec << 29) >> 31)) ^ + (*(mat++) & ((vec << 28) >> 31)) ^ + (*(mat++) & ((vec << 27) >> 31)) ^ + (*(mat++) & ((vec << 26) >> 31)) ^ + (*(mat++) & ((vec << 25) >> 31)) ^ + (*(mat++) & ((vec << 24) >> 31)) ^ + (*(mat++) & ((vec << 23) >> 31)) ^ + (*(mat++) & ((vec << 22) >> 31)) ^ + (*(mat++) & ((vec << 21) >> 31)) ^ + (*(mat++) & ((vec << 20) >> 31)) ^ + (*(mat++) & ((vec << 19) >> 31)) ^ + (*(mat++) & ((vec << 18) >> 31)) ^ + (*(mat++) & ((vec << 17) >> 31)) ^ + (*(mat++) & ((vec << 16) >> 31)) ^ + (*(mat++) & ((vec << 15) >> 31)) ^ + (*(mat++) & ((vec << 14) >> 31)) ^ + (*(mat++) & ((vec << 13) >> 31)) ^ + (*(mat++) & ((vec << 12) >> 31)) ^ + (*(mat++) & ((vec << 11) >> 31)) ^ + (*(mat++) & ((vec << 10) >> 31)) ^ + (*(mat++) & ((vec << 09) >> 31)) ^ + (*(mat++) & ((vec << 08) >> 31)) ^ + (*(mat++) & ((vec << 07) >> 31)) ^ + (*(mat++) & ((vec << 06) >> 31)) ^ + (*(mat++) & ((vec << 05) >> 31)) ^ + (*(mat++) & ((vec << 04) >> 31)) ^ + (*(mat++) & ((vec << 03) >> 31)) ^ + (*(mat++) & ((vec << 02) >> 31)) ^ + (*(mat++) & ((vec << 01) >> 31)) ^ + (*(mat++) & (vec >> 31))); + } + + /* ========================================================================= */ + private static unsafe void gf2_matrix_square(uint *square, uint *mat) + { + for (int n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); + } + + public static unsafe uint Combine(uint crc1, uint crc2, long len2) + { + /* degenerate case */ + if (len2 == 0) + return crc1; + if (crc1 == 0) + return crc2; + if (len2 < 0) + throw new ArgumentException("crc.Combine length cannot be negative", "len2"); + + fixed (uint* ct = &combineTable[0, 0]) + { + int n = 3; + do + { + /* apply zeros operator for this bit of len2 */ + if ((len2 & 1) != 0) + crc1 = gf2_matrix_times(ct + 32 * n, crc1); + len2 >>= 1; + n = (n + 1) & (GF2_DIM - 1); + /* if no more bits set, then done */ + } while (len2 != 0); + } + + /* return combined crc */ + crc1 ^= crc2; + return crc1; + } + + public static unsafe uint Subtract(uint crc1, uint crc2, long len2) + { + /* degenerate case */ + if (len2 == 0) + return crc1; + if (len2 < 0) + throw new ArgumentException("crc.Combine length cannot be negative", "len2"); + + crc1 ^= crc2; + + fixed (uint* st = &substractTable[0, 0]) + { + int n = 3; + do + { + /* apply zeros operator for this bit of len2 */ + if ((len2 & 1) != 0) + crc1 = gf2_matrix_times(st + 32 * n, crc1); + len2 >>= 1; + n = (n + 1) & (GF2_DIM - 1); + /* if no more bits set, then done */ + } while (len2 != 0); + } + + /* return combined crc */ + return crc1; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC8.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC8.cs new file mode 100644 index 000000000..2b2914d0b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CRC/CRC8.cs @@ -0,0 +1,43 @@ +namespace CUETools.Codecs +{ + public class Crc8 + { + private const ushort poly8 = 0x07; + + private ushort[] table = new ushort[256]; + + public Crc8() + { + int bits = 8; + ushort poly = (ushort) (poly8 + (1U << bits)); + for (ushort i = 0; i < table.Length; i++) + { + ushort crc = i; + for (int j = 0; j < bits; j++) + { + if ((crc & (1U << (bits - 1))) != 0) + crc = (ushort)((crc << 1) ^ poly); + else + crc <<= 1; + } + table[i] = (ushort)(crc & 0x00ff); + } + } + + public byte ComputeChecksum(byte[] bytes, int pos, int count) + { + ushort crc = 0; + for (int i = pos; i < pos + count; i++) + crc = table[crc ^ bytes[i]]; + return (byte)crc; + } + + public unsafe byte ComputeChecksum(byte* bytes, int pos, int count) + { + ushort crc = 0; + for (int i = pos; i < pos + count; i++) + crc = table[crc ^ bytes[i]]; + return (byte)crc; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CUETools.Codecs.csproj b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUETools.Codecs.csproj new file mode 100644 index 000000000..a7491076c --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUETools.Codecs.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + 2.1.9.0 + CUETools.Codecs + CUETools.Codecs + CUETools + A library for encoding and decoding audio. + Copyright (c) 2008-2021 Grigory Chudov + Grigory Chudov + true + ..\bin\$(Configuration)\ + https://github.com/gchudov/cuetools.net + git + + + + + + + + diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsCodecsConfig.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsCodecsConfig.cs new file mode 100644 index 000000000..0bf0103a0 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsCodecsConfig.cs @@ -0,0 +1,94 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Xml; +using System.Xml.Serialization; + +namespace CUETools.Codecs +{ + public class CUEToolsCodecsConfig + { + [JsonIgnore] + public Dictionary formats; + public List encoders; + public List decoders; + [JsonIgnore] + public EncoderListViewModel encodersViewModel; + [JsonIgnore] + public DecoderListViewModel decodersViewModel; + + public CUEToolsCodecsConfig() + { + encoders = new List(); + decoders = new List(); + encodersViewModel = new EncoderListViewModel(encoders); + decodersViewModel = new DecoderListViewModel(decoders); + formats = new Dictionary(); + } + + public CUEToolsCodecsConfig(CUEToolsCodecsConfig src) + { + encoders = new List(); + decoders = new List(); + src.encoders.ForEach(item => encoders.Add(item.Clone())); + src.decoders.ForEach(item => decoders.Add(item.Clone())); + encodersViewModel = new EncoderListViewModel(encoders); + decodersViewModel = new DecoderListViewModel(decoders); + formats = new Dictionary(); + foreach (var fmt in src.formats) + formats.Add(fmt.Key, fmt.Value.Clone(this)); + } + + public void Init(List src_encoders, List src_decoders) + { + encoders = new List(); + decoders = new List(); + src_encoders.ForEach(item => encoders.Add(item.Clone())); + src_decoders.ForEach(item => decoders.Add(item.Clone())); + + if (Type.GetType("Mono.Runtime", false) == null) + { + encoders.Add(new CommandLine.EncoderSettings("flake.exe", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); + encoders.Add(new CommandLine.EncoderSettings("takc.exe", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); + encoders.Add(new CommandLine.EncoderSettings("ffmpeg.exe", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (VBR)", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (CBR)", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); + encoders.Add(new CommandLine.EncoderSettings("oggenc.exe", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); + encoders.Add(new CommandLine.EncoderSettings("opusenc.exe", "opus", false, "6 16 32 48 64 96 128 192 256", "128", "opusenc.exe", "--bitrate %M - %O")); + encoders.Add(new CommandLine.EncoderSettings("neroAacEnc.exe", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); + encoders.Add(new CommandLine.EncoderSettings("qaac.exe (tvbr)", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); + + decoders.Add(new CommandLine.DecoderSettings("takc.exe", "tak", "takc.exe", "-d %I -")); + decoders.Add(new CommandLine.DecoderSettings("ffmpeg.exe", "m4a", "ffmpeg.exe", "-v 0 -i %I -f wav -")); + } + else + { + // !!! + } + + encodersViewModel = new EncoderListViewModel(encoders); + decodersViewModel = new DecoderListViewModel(decoders); + + formats = new Dictionary(); + formats.Add("flac", new CUEToolsFormat("flac", CUEToolsTagger.TagLibSharp, true, false, true, true, encodersViewModel.GetDefault("flac", true), null, decodersViewModel.GetDefault("flac"))); + formats.Add("wv", new CUEToolsFormat("wv", CUEToolsTagger.TagLibSharp, true, false, true, true, encodersViewModel.GetDefault("wv", true), null, decodersViewModel.GetDefault("wv"))); + formats.Add("ape", new CUEToolsFormat("ape", CUEToolsTagger.TagLibSharp, true, false, true, true, encodersViewModel.GetDefault("ape", true), null, decodersViewModel.GetDefault("ape"))); + formats.Add("tta", new CUEToolsFormat("tta", CUEToolsTagger.APEv2, true, false, false, true, encodersViewModel.GetDefault("tta", true), null, decodersViewModel.GetDefault("tta"))); + formats.Add("m2ts", new CUEToolsFormat("m2ts", CUEToolsTagger.APEv2, true, false, false, true, null, null, decodersViewModel.GetDefault("m2ts"))); + formats.Add("mpls", new CUEToolsFormat("mpls", CUEToolsTagger.APEv2, true, false, false, true, null, null, decodersViewModel.GetDefault("mpls"))); + formats.Add("wav", new CUEToolsFormat("wav", CUEToolsTagger.TagLibSharp, true, false, false, true, encodersViewModel.GetDefault("wav", true), null, decodersViewModel.GetDefault("wav"))); + formats.Add("m4a", new CUEToolsFormat("m4a", CUEToolsTagger.TagLibSharp, true, true, false, true, encodersViewModel.GetDefault("m4a", true), encodersViewModel.GetDefault("m4a", false), decodersViewModel.GetDefault("m4a"))); + formats.Add("tak", new CUEToolsFormat("tak", CUEToolsTagger.APEv2, true, false, true, true, encodersViewModel.GetDefault("tak", true), null, decodersViewModel.GetDefault("tak"))); + formats.Add("wma", new CUEToolsFormat("wma", CUEToolsTagger.TagLibSharp, true, true, false, true, encodersViewModel.GetDefault("wma", true), encodersViewModel.GetDefault("wma", false), decodersViewModel.GetDefault("wma"))); + formats.Add("mp3", new CUEToolsFormat("mp3", CUEToolsTagger.TagLibSharp, false, true, false, true, null, encodersViewModel.GetDefault("mp3", false), null)); + formats.Add("ogg", new CUEToolsFormat("ogg", CUEToolsTagger.TagLibSharp, false, true, false, true, null, encodersViewModel.GetDefault("ogg", false), null)); + formats.Add("opus", new CUEToolsFormat("opus", CUEToolsTagger.TagLibSharp, false, true, false, true, null, encodersViewModel.GetDefault("opus", false), null)); + formats.Add("mlp", new CUEToolsFormat("mlp", CUEToolsTagger.APEv2, true, false, false, false, null, null, decodersViewModel.GetDefault("mlp"))); + formats.Add("aob", new CUEToolsFormat("aob", CUEToolsTagger.APEv2, true, false, false, false, null, null, decodersViewModel.GetDefault("aob"))); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsFormat.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsFormat.cs new file mode 100644 index 000000000..b42cf9b30 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsFormat.cs @@ -0,0 +1,55 @@ +namespace CUETools.Codecs +{ + public class CUEToolsFormat + { + public CUEToolsFormat( + string _extension, + CUEToolsTagger _tagger, + bool _allowLossless, + bool _allowLossy, + bool _allowEmbed, + bool _builtin, + AudioEncoderSettingsViewModel _encoderLossless, + AudioEncoderSettingsViewModel _encoderLossy, + AudioDecoderSettingsViewModel _decoder) + { + extension = _extension; + tagger = _tagger; + allowLossless = _allowLossless; + allowLossy = _allowLossy; + allowEmbed = _allowEmbed; + builtin = _builtin; + encoderLossless = _encoderLossless; + encoderLossy = _encoderLossy; + decoder = _decoder; + } + public string DotExtension + { + get + { + return "." + extension; + } + } + + public CUEToolsFormat Clone(CUEToolsCodecsConfig cfg) + { + var res = this.MemberwiseClone() as CUEToolsFormat; + if (decoder != null) cfg.decodersViewModel.TryGetValue(decoder.Settings.Extension, decoder.Settings.Name, out res.decoder); + if (encoderLossy != null) cfg.encodersViewModel.TryGetValue(encoderLossy.Settings.Extension, encoderLossy.Lossless, encoderLossy.Settings.Name, out res.encoderLossy); + if (encoderLossless != null) cfg.encodersViewModel.TryGetValue(encoderLossless.Settings.Extension, encoderLossless.Lossless, encoderLossless.Settings.Name, out res.encoderLossless); + return res; + } + + public override string ToString() + { + return extension; + } + + public string extension; + public AudioEncoderSettingsViewModel encoderLossless; + public AudioEncoderSettingsViewModel encoderLossy; + public AudioDecoderSettingsViewModel decoder; + public CUEToolsTagger tagger; + public bool allowLossless, allowLossy, allowEmbed, builtin; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsTagger.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsTagger.cs new file mode 100644 index 000000000..d0354bf3b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CUEToolsTagger.cs @@ -0,0 +1,9 @@ +namespace CUETools.Codecs +{ + public enum CUEToolsTagger + { + TagLibSharp = 0, + APEv2 = 1, + ID3v2 = 2, + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioDecoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioDecoder.cs new file mode 100644 index 000000000..91ea5c186 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioDecoder.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace CUETools.Codecs.CommandLine +{ + public class AudioDecoder : IAudioSource + { + string _path; + Process _decoderProcess; + WAV.AudioDecoder rdr; + + private DecoderSettings m_settings; + public IAudioDecoderSettings Settings => m_settings; + + public long Position + { + get + { + Initialize(); + return rdr.Position; + } + set + { + Initialize(); + rdr.Position = value; + } + } + + public TimeSpan Duration + { + get + { + Initialize(); + return rdr.Duration; + } + } + + public long Length + { + get + { + Initialize(); + return rdr.Length; + } + } + + public long Remaining + { + get + { + Initialize(); + return rdr.Remaining; + } + } + + public AudioPCMConfig PCM + { + get + { + Initialize(); + return rdr.PCM; + } + } + + public string Path { get { return _path; } } + + public AudioDecoder(DecoderSettings settings, string path, Stream IO) + { + m_settings = settings; + _path = path; + _decoderProcess = null; + rdr = null; + } + + void Initialize() + { + if (_decoderProcess != null) + return; + _decoderProcess = new Process(); + _decoderProcess.StartInfo.FileName = m_settings.Path; + _decoderProcess.StartInfo.Arguments = m_settings.Parameters.Replace("%I", "\"" + _path + "\""); + _decoderProcess.StartInfo.CreateNoWindow = true; + _decoderProcess.StartInfo.RedirectStandardOutput = true; + _decoderProcess.StartInfo.UseShellExecute = false; + bool started = false; + Exception ex = null; + try + { + started = _decoderProcess.Start(); + if (started) + _decoderProcess.PriorityClass = Process.GetCurrentProcess().PriorityClass; + } + catch (Exception _ex) + { + ex = _ex; + } + if (!started) + { + _decoderProcess = null; + throw new Exception(m_settings.Path + ": " + (ex == null ? "please check the path" : ex.Message)); + } + rdr = new WAV.AudioDecoder(new WAV.DecoderSettings(), _path, _decoderProcess.StandardOutput.BaseStream); + } + + public void Close() + { + if (rdr != null) + rdr.Close(); + if (_decoderProcess != null && !_decoderProcess.HasExited) + try { _decoderProcess.Kill(); _decoderProcess.WaitForExit(); } + catch { } + } + + public int Read(AudioBuffer buff, int maxLength) + { + Initialize(); + return rdr.Read(buff, maxLength); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioEncoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioEncoder.cs new file mode 100644 index 000000000..24d23ad2d --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/AudioEncoder.cs @@ -0,0 +1,137 @@ +using System; +using System.Diagnostics; +using System.IO; + +namespace CUETools.Codecs.CommandLine +{ + public class AudioEncoder : IAudioDest + { + string _path; + Process _encoderProcess; + WAV.AudioEncoder wrt; + CyclicBuffer outputBuffer = null; + bool useTempFile = false; + string tempFile = null; + long _finalSampleCount = -1; + bool closed = false; + + public long Position + { + get + { + return wrt.Position; + } + } + + public long FinalSampleCount + { + set { _finalSampleCount = wrt.FinalSampleCount = value; } + } + + // !!!! Must not start the process in constructor, so that we can set CompressionLevel via Settings! + private EncoderSettings m_settings; + public IAudioEncoderSettings Settings => m_settings; + + public string Path { get { return _path; } } + + public AudioEncoder(EncoderSettings settings, string path, Stream IO = null) + { + m_settings = settings; + _path = path; + useTempFile = m_settings.Parameters.Contains("%I"); + tempFile = path + ".tmp.wav"; + + _encoderProcess = new Process(); + _encoderProcess.StartInfo.FileName = m_settings.Path; + _encoderProcess.StartInfo.Arguments = m_settings.Parameters.Replace("%O", "\"" + path + "\"").Replace("%M", m_settings.EncoderMode).Replace("%P", m_settings.Padding.ToString()).Replace("%I", "\"" + tempFile + "\""); + _encoderProcess.StartInfo.CreateNoWindow = true; + if (!useTempFile) + _encoderProcess.StartInfo.RedirectStandardInput = true; + _encoderProcess.StartInfo.UseShellExecute = false; + if (!m_settings.Parameters.Contains("%O")) + _encoderProcess.StartInfo.RedirectStandardOutput = true; + if (useTempFile) + { + wrt = new WAV.AudioEncoder(new WAV.EncoderSettings(settings.PCM), tempFile); + return; + } + bool started = false; + Exception ex = null; + try + { + started = _encoderProcess.Start(); + if (started) + _encoderProcess.PriorityClass = Process.GetCurrentProcess().PriorityClass; + } + catch (Exception _ex) + { + ex = _ex; + } + if (!started) + throw new Exception(m_settings.Path + ": " + (ex == null ? "please check the path" : ex.Message)); + if (_encoderProcess.StartInfo.RedirectStandardOutput) + { + Stream outputStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); + outputBuffer = new CyclicBuffer(2 * 1024 * 1024, _encoderProcess.StandardOutput.BaseStream, outputStream); + } + Stream inputStream = new CyclicBufferOutputStream(_encoderProcess.StandardInput.BaseStream, 128 * 1024); + wrt = new WAV.AudioEncoder(new WAV.EncoderSettings(settings.PCM), path, inputStream); + } + + public void Close() + { + if (closed) + return; + closed = true; + wrt.Close(); + if (useTempFile && (_finalSampleCount < 0 || wrt.Position == _finalSampleCount)) + { + bool started = false; + Exception ex = null; + try + { + started = _encoderProcess.Start(); + if (started) + _encoderProcess.PriorityClass = Process.GetCurrentProcess().PriorityClass; + } + catch (Exception _ex) + { + ex = _ex; + } + if (!started) + throw new Exception(m_settings.Path + ": " + (ex == null ? "please check the path" : ex.Message)); + } + wrt = null; + if (!_encoderProcess.HasExited) + _encoderProcess.WaitForExit(); + if (useTempFile) + File.Delete(tempFile); + if (outputBuffer != null) + outputBuffer.Close(); + if (_encoderProcess.ExitCode != 0) + throw new Exception(String.Format("{0} returned error code {1}", m_settings.Path, _encoderProcess.ExitCode)); + } + + public void Delete() + { + Close(); + File.Delete(_path); + } + + public void Write(AudioBuffer buff) + { + try + { + wrt.Write(buff); + } + catch (IOException ex) + { + if (_encoderProcess.HasExited) + throw new IOException(string.Format("{0} has exited prematurely with code {1}", m_settings.Path, _encoderProcess.ExitCode), ex); + else + throw ex; + } + //_sampleLen += sampleCount; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/DecoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/DecoderSettings.cs new file mode 100644 index 000000000..9bdba0d58 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/DecoderSettings.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.ComponentModel; +using Newtonsoft.Json; + +namespace CUETools.Codecs.CommandLine +{ + [JsonObject(MemberSerialization.OptIn)] + public class DecoderSettings : IAudioDecoderSettings + { + #region IAudioDecoderSettings implementation + [DefaultValue("")] + [JsonProperty] + public string Name { get; set; } + + [DefaultValue("")] + [JsonProperty] + public string Extension { get; set; } + + [Browsable(false)] + public Type DecoderType => typeof(AudioDecoder); + + [Browsable(false)] + public int Priority => 2; + + public IAudioDecoderSettings Clone() + { + return MemberwiseClone() as IAudioDecoderSettings; + } + #endregion + + public DecoderSettings() + { + this.Init(); + } + + public DecoderSettings( + string name, + string extension, + string path, + string parameters) + : base() + { + Name = name; + Extension = extension; + Path = path; + Parameters = parameters; + } + + [DefaultValue("")] + [JsonProperty] + public string Path + { + get; + set; + } + + [DefaultValue("")] + [JsonProperty] + public string Parameters + { + get; + set; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/EncoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/EncoderSettings.cs new file mode 100644 index 000000000..004e7bfb0 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CommandLine/EncoderSettings.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.ComponentModel; +using Newtonsoft.Json; + +namespace CUETools.Codecs.CommandLine +{ + [JsonObject(MemberSerialization.OptIn)] + public class EncoderSettings : IAudioEncoderSettings + { + #region IAudioEncoderSettings implementation + [DefaultValue("")] + [JsonProperty] + public string Name { get; set; } + + [DefaultValue("")] + [JsonProperty] + public string Extension { get; set; } + + [Browsable(false)] + public Type EncoderType => typeof(AudioEncoder); + + [JsonProperty] + public bool Lossless { get; set; } + + [Browsable(false)] + public int Priority => 0; + + [DefaultValue("")] + [JsonProperty] + public string SupportedModes { get; set; } + + public string DefaultMode => EncoderMode; + + [Browsable(false)] + [DefaultValue("")] + [JsonProperty] + public string EncoderMode { get; set; } + + [Browsable(false)] + public AudioPCMConfig PCM { get; set; } + + [Browsable(false)] + public int BlockSize { get; set; } + + [Browsable(false)] + [DefaultValue(4096)] + public int Padding { get; set; } + + public IAudioEncoderSettings Clone() + { + return MemberwiseClone() as IAudioEncoderSettings; + } + #endregion + + public EncoderSettings() + { + this.Init(); + } + + public EncoderSettings( + string name, + string extension, + bool lossless, + string supportedModes, + string defaultMode, + string path, + string parameters + ) + { + this.Init(); + Name = name; + Extension = extension; + Lossless = lossless; + SupportedModes = supportedModes; + Path = path; + EncoderMode = defaultMode; + Parameters = parameters; + } + + [DefaultValue("")] + [JsonProperty] + public string Path + { + get; + set; + } + + [DefaultValue("")] + [JsonProperty] + public string Parameters + { + get; + set; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBuffer.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBuffer.cs new file mode 100644 index 000000000..5c68c2c89 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBuffer.cs @@ -0,0 +1,231 @@ +using System; +using System.IO; +using System.Threading; + +namespace CUETools.Codecs +{ + public class CyclicBuffer + { + private byte[] _buffer; + private int _size; + private int _start = 0; // moved only by Write + private int _end = 0; // moved only by Read + private bool _eof = false; + private Thread _readThread = null, _writeThread = null; + private Exception _ex = null; + + private int DataAvailable + { + get + { + return _end - _start; + } + } + + private int FreeSpace + { + get + { + return _size - DataAvailable; + } + } + + public CyclicBuffer(int len) + { + _size = len; + _buffer = new byte[len]; + } + + public CyclicBuffer(int len, Stream input, Stream output) + { + _size = len; + _buffer = new byte[len]; + ReadFrom(input); + WriteTo(output); + } + + public void ReadFrom(Stream input) + { + _readThread = new Thread(PumpRead); + _readThread.Priority = ThreadPriority.Highest; + _readThread.IsBackground = true; + _readThread.Start(input); + } + + public void WriteTo(Stream output) + { + WriteTo(flushOutputToStream, closeOutputToStream, ThreadPriority.Highest, output); + } + + public void WriteTo(FlushOutput flushOutputDelegate, CloseOutput closeOutputDelegate, ThreadPriority priority, object to) + { + if (flushOutputDelegate != null) + flushOutput += flushOutputDelegate; + if (closeOutputDelegate != null) + closeOutput += closeOutputDelegate; + _writeThread = new Thread(FlushThread); + _writeThread.Priority = priority; + _writeThread.IsBackground = true; + _writeThread.Start(to); + } + + private void closeOutputToStream(object to) + { + ((Stream)to).Close(); + } + + private void flushOutputToStream(byte[] buffer, int pos, int chunk, object to) + { + ((Stream)to).Write(buffer, pos, chunk); + } + + private void PumpRead(object o) + { + while (Read((Stream)o)) + ; + SetEOF(); + } + + public void Close() + { + if (_readThread != null) + { + _readThread.Join(); + _readThread = null; + } + SetEOF(); + if (_writeThread != null) + { + _writeThread.Join(); + _writeThread = null; + } + } + + public void SetEOF() + { + lock (this) + { + _eof = true; + Monitor.Pulse(this); + } + } + + public bool Read(Stream input) + { + int pos, chunk; + lock (this) + { + while (FreeSpace == 0 && _ex == null) + Monitor.Wait(this); + if (_ex != null) + throw _ex; + pos = _end % _size; + chunk = Math.Min(FreeSpace, _size - pos); + } + chunk = input.Read(_buffer, pos, chunk); + if (chunk == 0) + return false; + lock (this) + { + _end += chunk; + Monitor.Pulse(this); + } + return true; + } + + public void Read(byte[] array, int offset, int count) + { + int pos, chunk; + while (count > 0) + { + lock (this) + { + while (FreeSpace == 0 && _ex == null) + Monitor.Wait(this); + if (_ex != null) + throw _ex; + pos = _end % _size; + chunk = Math.Min(FreeSpace, _size - pos); + chunk = Math.Min(chunk, count); + } + Array.Copy(array, offset, _buffer, pos, chunk); + lock (this) + { + _end += chunk; + Monitor.Pulse(this); + } + count -= chunk; + offset += chunk; + } + } + + public void Write(byte[] buff, int offs, int count) + { + while (count > 0) + { + int pos, chunk; + lock (this) + { + while (DataAvailable == 0 && !_eof) + Monitor.Wait(this); + if (DataAvailable == 0) + break; + pos = _start % _size; + chunk = Math.Min(DataAvailable, _size - pos); + } + if (flushOutput != null) + Array.Copy(_buffer, pos, buff, offs, chunk); + offs += chunk; + lock (this) + { + _start += chunk; + Monitor.Pulse(this); + } + } + } + + private void FlushThread(object to) + { + while (true) + { + int pos, chunk; + lock (this) + { + while (DataAvailable == 0 && !_eof) + Monitor.Wait(this); + if (DataAvailable == 0) + break; + pos = _start % _size; + chunk = Math.Min(DataAvailable, _size - pos); + } + if (flushOutput != null) + try + { + flushOutput(_buffer, pos, chunk, to); + } + catch (Exception ex) + { + lock (this) + { + _ex = ex; + Monitor.Pulse(this); + return; + } + } + lock (this) + { + _start += chunk; + Monitor.Pulse(this); + } + } + if (closeOutput != null) + closeOutput(to); + } + + public delegate void FlushOutput(byte[] buffer, int pos, int chunk, object to); + public delegate void CloseOutput(object to); + + public event FlushOutput flushOutput; + public event CloseOutput closeOutput; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferInputStream.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferInputStream.cs new file mode 100644 index 000000000..05309a03e --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferInputStream.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; + +namespace CUETools.Codecs +{ + public class CyclicBufferInputStream : Stream + { + private CyclicBuffer _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 + { + throw new NotSupportedException(); + } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public CyclicBufferInputStream(CyclicBuffer buffer) + { + _buffer = buffer; + } + + public override void Close() + { + _buffer.Close(); + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] array, int offset, int count) + { + _buffer.Write(array, offset, count); + return count; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] array, int offset, int count) + { + throw new NotSupportedException(); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferOutputStream.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferOutputStream.cs new file mode 100644 index 000000000..4c18c0dd7 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/CyclicBufferOutputStream.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; + +namespace CUETools.Codecs +{ + public class CyclicBufferOutputStream : Stream + { + private CyclicBuffer _buffer; + + public override bool CanRead + { + get { return false; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public CyclicBufferOutputStream(CyclicBuffer buffer) + { + _buffer = buffer; + } + + public CyclicBufferOutputStream(Stream output, int size) + { + _buffer = new CyclicBuffer(size); + _buffer.WriteTo(output); + } + + public override void Close() + { + _buffer.Close(); + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] array, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] array, int offset, int count) + { + _buffer.Read(array, offset, count); + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/DefaultValueForMode.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/DefaultValueForMode.cs new file mode 100644 index 000000000..f99c88679 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/DefaultValueForMode.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Codecs +{ + /// + /// Default property value for each encoder mode attribute + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class DefaultValueForModeAttribute : Attribute + { + /// + /// Resource manager to use; + /// + public object[] m_values; + + /// + /// Construct the description attribute + /// + /// + public DefaultValueForModeAttribute(params object[] values) + { + this.m_values = values; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioDest.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioDest.cs new file mode 100644 index 000000000..018d27ae7 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioDest.cs @@ -0,0 +1,14 @@ +namespace CUETools.Codecs +{ + public interface IAudioDest + { + IAudioEncoderSettings Settings { get; } + + string Path { get; } + long FinalSampleCount { set; } + + void Write(AudioBuffer buffer); + void Close(); + void Delete(); + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioFilter.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioFilter.cs new file mode 100644 index 000000000..2f1eeceb4 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioFilter.cs @@ -0,0 +1,7 @@ +namespace CUETools.Codecs +{ + public interface IAudioFilter + { + IAudioDest AudioDest { set; } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioSource.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioSource.cs new file mode 100644 index 000000000..ef88362dc --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/IAudioSource.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; + +namespace CUETools.Codecs +{ + public interface IAudioSource + { + IAudioDecoderSettings Settings { get; } + + AudioPCMConfig PCM { get; } + string Path { get; } + + TimeSpan Duration { get; } + long Length { get; } + long Position { get; set; } + long Remaining { get; } + + int Read(AudioBuffer buffer, int maxLength); + void Close(); + } + + public interface IAudioTitle + { + List Chapters { get; } + AudioPCMConfig PCM { get; } + string Codec { get; } + string Language { get; } + int StreamId { get; } + //IAudioSource Open { get; } + } + + public interface IAudioTitleSet + { + List AudioTitles { get; } + } + + public static class IAudioTitleExtensions + { + public static TimeSpan GetDuration(this IAudioTitle title) + { + var chapters = title.Chapters; + return chapters[chapters.Count - 1]; + } + + + public static string GetRateString(this IAudioTitle title) + { + var sr = title.PCM.SampleRate; + if (sr % 1000 == 0) return $"{sr / 1000}KHz"; + if (sr % 100 == 0) return $"{sr / 100}.{(sr / 100) % 10}KHz"; + return $"{sr}Hz"; + } + + public static string GetFormatString(this IAudioTitle title) + { + switch (title.PCM.ChannelCount) + { + case 1: return "mono"; + case 2: return "stereo"; + default: return "multi-channel"; + } + } + } + + public class SingleAudioTitle : IAudioTitle + { + public SingleAudioTitle(IAudioSource source) { this.source = source; } + public List Chapters => new List { TimeSpan.Zero, source.Duration }; + public AudioPCMConfig PCM => source.PCM; + public string Codec => source.Settings.Extension; + public string Language => ""; + public int StreamId => 0; + IAudioSource source; + } + + public class SingleAudioTitleSet : IAudioTitleSet + { + public SingleAudioTitleSet(IAudioSource source) { this.source = source; } + public List AudioTitles => new List { new SingleAudioTitle(source) }; + IAudioSource source; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/IWavePlayer.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/IWavePlayer.cs new file mode 100644 index 000000000..6133d853c --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/IWavePlayer.cs @@ -0,0 +1,41 @@ +using System; + +namespace CUETools.Codecs +{ + /// + /// Represents the interface to a device that can play a WaveFile + /// + public interface IWavePlayer : IDisposable, IAudioDest + { + /// + /// Begin playback + /// + void Play(); + + /// + /// Stop playback + /// + void Stop(); + + /// + /// Pause Playback + /// + void Pause(); + + /// + /// Current playback state + /// + PlaybackState PlaybackState { get; } + + /// + /// The volume 1.0 is full scale + /// + float Volume { get; set; } + + /// + /// Indicates that playback has gone into a stopped state due to + /// reaching the end of the input stream + /// + event EventHandler PlaybackStopped; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/LPC.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/LPC.cs new file mode 100644 index 000000000..90cc56d28 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/LPC.cs @@ -0,0 +1,1182 @@ +using System; + +namespace CUETools.Codecs +{ + public class lpc + { + public const int MAX_LPC_ORDER = 32; + public const int MAX_LPC_WINDOWS = 16; + public const int MAX_LPC_PRECISIONS = 4; + public const int MAX_LPC_SECTIONS = 128; + + public unsafe static void window_welch(float* window, int L) + { + int N = L - 1; + double N2 = (double)N / 2.0; + + for (int n = 0; n <= N; n++) + { + double k = ((double)n - N2) / N2; + k = 1.0 - k * k; + window[n] = (float)(k); + } + } + + public unsafe static void window_bartlett(float* window, int L) + { + int N = L - 1; + double N2 = (double)N / 2.0; + for (int n = 0; n <= N; n++) + { + double k = ((double)n - N2) / N2; + k = 1.0 - k * k; + window[n] = (float)(k * k); + } + } + + public unsafe static void window_rectangle(float* window, int L) + { + for (int n = 0; n < L; n++) + window[n] = 1.0F; + } + + public unsafe static void window_flattop(float* window, int L) + { + int N = L - 1; + for (int n = 0; n < L; n++) + window[n] = (float)(1.0 - 1.93 * Math.Cos(2.0 * Math.PI * n / N) + 1.29 * Math.Cos(4.0 * Math.PI * n / N) - 0.388 * Math.Cos(6.0 * Math.PI * n / N) + 0.0322 * Math.Cos(8.0 * Math.PI * n / N)); + } + + public unsafe static void window_tukey(float* window, int L, double p) + { + int z = 0; + int Np = (int)(p / 2.0 * L) - z; + if (Np > 0) + { + for (int n = 0; n < z; n++) + window[n] = window[L - n - 1] = 0; + for (int n = 0; n < Np - 1; n++) + window[n + z] = window[L - n - 1 - z] = (float)(0.5 - 0.5 * Math.Cos(Math.PI * (n + 1) / Np)); + for (int n = z + Np - 1; n < L - z - Np + 1; n++) + window[n] = 1.0F; + } + } + + public unsafe static void window_punchout_tukey(float* window, int L, double p, double p1, double start, double end) + { + int start_n = (int)(start * L); + int end_n = (int)(end * L); + int Np = (int)(p / 2.0 * L); + int Np1 = (int)(p1 / 2.0 * L); + int i, n = 0; + + if (start_n != 0) + { + for (i = 1; n < Np; n++, i++) + window[n] = (float)(0.5 - 0.5 * Math.Cos(Math.PI * i / Np)); + for (; n < start_n - Np1; n++) + window[n] = 1.0f; + for (i = Np1; n < start_n; n++, i--) + window[n] = (float)(0.5 - 0.5 * Math.Cos(Math.PI * i / Np1)); + } + for (; n < end_n; n++) + window[n] = 0.0f; + if (end_n != L) + { + for (i = 1; n < end_n + Np1; n++, i++) + window[n] = (float)(0.5 - 0.5 * Math.Cos(Math.PI * i / Np1)); + for (; n < L - Np; n++) + window[n] = 1.0f; + for (i = Np; n < L; n++, i--) + window[n] = (float)(0.5 - 0.5 * Math.Cos(Math.PI * i / Np)); + } + } + + public unsafe static void window_hann(float* window, int L) + { + int N = L - 1; + for (int n = 0; n < L; n++) + window[n] = (float)(0.5 - 0.5 * Math.Cos(2.0 * Math.PI * n / N)); + } + + private static short sign_only(int val) + { + return (short)((val >> 31) + ((val - 1) >> 31) + 1); + } + +#if XXX + static public unsafe void + compute_corr_int(/*const*/ short* data1, short* data2, int len, int min, int lag, int* autoc) + { + for (int i = min; i <= lag; ++i) + { + int temp = 0; + int temp2 = 0; + + for (int j = 0; j <= lag - i; ++j) + temp += data1[j + i] * data2[j]; + + for (int j = lag + 1 - i; j < len - i; j += 2) + { + temp += data1[j + i] * data2[j]; + temp2 += data1[j + i + 1] * data2[j + 1]; + } + autoc[i] = temp + temp2; + } + } +#endif + + /** + * Calculates autocorrelation data from audio samples + * A window function is applied before calculation. + */ + static public unsafe void + compute_autocorr(/*const*/ int* data, float* window, int len, int min, int lag, double* autoc) + { +#if FPAC + short* data1 = stackalloc short[len + 1]; + short* data2 = stackalloc short[len + 1]; + int* c1 = stackalloc int[lpc.MAX_LPC_ORDER + 1]; + int* c2 = stackalloc int[lpc.MAX_LPC_ORDER + 1]; + int* c3 = stackalloc int[lpc.MAX_LPC_ORDER + 1]; + int* c4 = stackalloc int[lpc.MAX_LPC_ORDER + 1]; + + for (int i = 0; i < len; i++) + { + int val = (int)(data[i] * window[i]); + data1[i] = (short)(sign_only(val) * (Math.Abs(val) >> 9)); + data2[i] = (short)(sign_only(val) * (Math.Abs(val) & 0x1ff)); + } + data1[len] = 0; + data2[len] = 0; + + compute_corr_int(data1, data1, len, min, lag, c1); + compute_corr_int(data1, data2, len, min, lag, c2); + compute_corr_int(data2, data1, len, min, lag, c3); + compute_corr_int(data2, data2, len, min, lag, c4); + + for (int coeff = min; coeff <= lag; coeff++) + autoc[coeff] = (c1[coeff] * (double)(1 << 18) + (c2[coeff] + c3[coeff]) * (double)(1 << 9) + c4[coeff]); +#else +#if XXX + if (min == 0 && lag >= 4) + { + int* pdata = data; + float* pwindow = window; + + double temp0 = 1.0; + double temp1 = 1.0; + double temp2 = 1.0; + double temp3 = 1.0; + double temp4 = 1.0; + + double c0 = *(pdata++) * *(pwindow++); + float c1 = *(pdata++) * *(pwindow++); + float c2 = *(pdata++) * *(pwindow++); + float c3 = *(pdata++) * *(pwindow++); + float c4 = *(pdata++) * *(pwindow++); + + int* finish = data + len; + + while (pdata <= finish) + { + temp0 += c0 * c0; + temp1 += c0 * c1; + temp2 += c0 * c2; + temp3 += c0 * c3; + temp4 += c0 * c4; + + c0 = c1; + c1 = c2; + c2 = c3; + c3 = c4; + c4 = *(pdata++) * *(pwindow++); + } + + temp0 += c0 * c0; + temp1 += c0 * c1; + temp2 += c0 * c2; + temp3 += c0 * c3; + c0 = c1; + c1 = c2; + c2 = c3; + temp0 += c0 * c0; + temp1 += c0 * c1; + temp2 += c0 * c2; + c0 = c1; + c1 = c2; + temp0 += c0 * c0; + temp1 += c0 * c1; + c0 = c1; + temp0 += c0 * c0; + + autoc[0] += temp0; + autoc[1] += temp1; + autoc[2] += temp2; + autoc[3] += temp3; + autoc[4] += temp4; + min = 5; + + if (lag < min) return; + } +#endif + double* data1 = stackalloc double[len]; + int i; + + for (i = 0; i < len; i++) + data1[i] = data[i] * window[i]; + + for (i = min; i <= lag; ++i) + { + double temp = 0; + double temp2 = 0; + double* pdata = data1; + double* finish = data1 + len - 1 - i; + + while (pdata < finish) + { + temp += pdata[i] * (*pdata++); + temp2 += pdata[i] * (*pdata++); + } + if (pdata <= finish) + temp += pdata[i] * (*pdata++); + + autoc[i] += temp + temp2; + } +#endif + } + + static public unsafe void + compute_autocorr_windowless(/*const*/ int* data, int len, int min, int lag, double* autoc) + { + // if databits*2 + log2(len) <= 64 +#if !XXX +#if XXX + if (min == 0 && lag >= 4) + { + long temp0 = 0; + long temp1 = 0; + long temp2 = 0; + long temp3 = 0; + long temp4 = 0; + int* pdata = data; + int* finish = data + len - 4; + while (pdata < finish) + { + long c0 = *(pdata++); + temp0 += c0 * c0; + temp1 += c0 * pdata[0]; + temp2 += c0 * pdata[1]; + temp3 += c0 * pdata[2]; + temp4 += c0 * pdata[3]; + } + { + long c0 = *(pdata++); + temp0 += c0 * c0; + temp1 += c0 * pdata[0]; + temp2 += c0 * pdata[1]; + temp3 += c0 * pdata[2]; + } + { + long c0 = *(pdata++); + temp0 += c0 * c0; + temp1 += c0 * pdata[0]; + temp2 += c0 * pdata[1]; + } + { + long c0 = *(pdata++); + temp0 += c0 * c0; + temp1 += c0 * pdata[0]; + } + { + long c0 = *(pdata++); + temp0 += c0 * c0; + } + autoc[0] += temp0; + autoc[1] += temp1; + autoc[2] += temp2; + autoc[3] += temp3; + autoc[4] += temp4; + min = 5; + + if (lag < min) return; + } +#endif + for (int i = min; i <= lag; ++i) + { + long temp = 0; + long temp2 = 0; + int* pdata = data; + int* finish = data + len - i - 1; + while (pdata < finish) + { + temp += (long)pdata[i] * (*pdata++); + temp2 += (long)pdata[i] * (*pdata++); + } + if (pdata <= finish) + temp += (long)pdata[i] * (*pdata++); + autoc[i] += temp + temp2; + } +#else + for (int i = min; i <= lag; ++i) + { + double temp = 0; + double temp2 = 0; + int* pdata = data; + int* finish = data + len - i - 1; + + while (pdata < finish) + { + temp += (double)pdata[i] * (double)(*pdata++); + temp2 += (double)pdata[i] * (double)(*pdata++); + } + if (pdata <= finish) + temp += (double)pdata[i] * (double)(*pdata++); + autoc[i] += temp + temp2; + } +#endif + } + + static public unsafe void + compute_autocorr_windowless_large(/*const*/ int* data, int len, int min, int lag, double* autoc) + { + for (int i = min; i <= lag; ++i) + { + double temp = 0; + double temp2 = 0; + int* pdata = data; + int* finish = data + len - i - 1; + while (pdata < finish) + { + temp += (long)pdata[i] * (*pdata++); + temp2 += (long)pdata[i] * (*pdata++); + } + if (pdata <= finish) + temp += (long)pdata[i] * (*pdata++); + autoc[i] += temp + temp2; + } + } + + static public unsafe void + compute_autocorr_glue(/*const*/ int* data, float* window, int offs, int offs1, int min, int lag, double* autoc) + { + double* data1 = stackalloc double[lag + lag]; + for (int i = -lag; i < lag; i++) + data1[i + lag] = offs + i >= 0 && offs + i < offs1 ? data[offs + i] * window[offs + i] : 0; + for (int i = min; i <= lag; ++i) + { + double temp = 0; + double* pdata = data1 + lag - i; + double* finish = data1 + lag; + while (pdata < finish) + temp += pdata[i] * (*pdata++); + autoc[i] += temp; + } + } + + static public unsafe void + compute_autocorr_glue(/*const*/ int* data, int min, int lag, double* autoc) + { + for (int i = min; i <= lag; ++i) + { + long temp = 0; + int* pdata = data - i; + int* finish = data; + while (pdata < finish) + temp += (long)pdata[i] * (*pdata++); + autoc[i] += temp; + } + } + + /** + * Levinson-Durbin recursion. + * Produces LPC coefficients from autocorrelation data. + */ + public static unsafe void + compute_lpc_coefs(uint max_order, double* reff, float* lpc/*[][MAX_LPC_ORDER]*/) + { + double* lpc_tmp = stackalloc double[MAX_LPC_ORDER]; + + if (max_order > MAX_LPC_ORDER) + throw new Exception("weird"); + + for (int i = 0; i < max_order; i++) + lpc_tmp[i] = 0; + + for (int i = 0; i < max_order; i++) + { + double r = reff[i]; + int i2 = (i >> 1); + lpc_tmp[i] = r; + for (int j = 0; j < i2; j++) + { + double tmp = lpc_tmp[j]; + lpc_tmp[j] += r * lpc_tmp[i - 1 - j]; + lpc_tmp[i - 1 - j] += r * tmp; + } + + if (0 != (i & 1)) + lpc_tmp[i2] += lpc_tmp[i2] * r; + + for (int j = 0; j <= i; j++) + lpc[i * MAX_LPC_ORDER + j] = (float)-lpc_tmp[j]; + } + } + + public static unsafe void + compute_schur_reflection(/*const*/ double* autoc, uint max_order, + double* reff/*[][MAX_LPC_ORDER]*/, double * err) + { + double* gen0 = stackalloc double[MAX_LPC_ORDER]; + double* gen1 = stackalloc double[MAX_LPC_ORDER]; + + // Schur recursion + for (uint i = 0; i < max_order; i++) + gen0[i] = gen1[i] = autoc[i + 1]; + + double error = autoc[0]; + reff[0] = -gen1[0] / error; + error += gen1[0] * reff[0]; + err[0] = error; + for (uint i = 1; i < max_order; i++) + { + for (uint j = 0; j < max_order - i; j++) + { + gen1[j] = gen1[j + 1] + reff[i - 1] * gen0[j]; + gen0[j] = gen1[j + 1] * reff[i - 1] + gen0[j]; + } + reff[i] = -gen1[0] / error; + error += gen1[0] * reff[i]; + err[i] = error; + } + } + + /** + * Quantize LPC coefficients + */ + public static unsafe void + quantize_lpc_coefs(float* lpc_in, int order, uint precision, int* lpc_out, + out int shift, int max_shift, int zero_shift) + { + int i; + float d, cmax, error; + int qmax; + int sh, q; + + // define maximum levels + qmax = (1 << ((int)precision - 1)) - 1; + + // find maximum coefficient value + cmax = 0.0F; + for (i = 0; i < order; i++) + { + d = Math.Abs(lpc_in[i]); + if (d > cmax) + cmax = d; + } + // if maximum value quantizes to zero, return all zeros + if (cmax * (1 << max_shift) < 1.0) + { + shift = zero_shift; + for (i = 0; i < order; i++) + lpc_out[i] = 0; + return; + } + + // calculate level shift which scales max coeff to available bits + sh = max_shift; + while ((cmax * (1 << sh) > qmax) && (sh > 0)) + { + sh--; + } + + // since negative shift values are unsupported in decoder, scale down + // coefficients instead + if (sh == 0 && cmax > qmax) + { + float scale = ((float)qmax) / cmax; + for (i = 0; i < order; i++) + { + lpc_in[i] *= scale; + } + } + + // output quantized coefficients and level shift + error = 0; + for (i = 0; i < order; i++) + { + error += lpc_in[i] * (1 << sh); + q = (int)(error + 0.5); + if (q < -(qmax+1)) q = -(qmax + 1); + if (q > qmax) q = qmax; + error -= q; + lpc_out[i] = q; + } + shift = sh; + } + + private static unsafe ulong + encode_residual_partition(int* s, int* r, int* seg_end, int* coefs, int shift, int order) + { + ulong sum = 0ul; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + while (s < seg_end) + { + int pred = c0 * *(s++); + //*(r++) = *s - (pred >> shift); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + } + break; + case 2: + while (s < seg_end) + { + int pred = c1 * *(s++); + pred += c0 * *(s++); + int d = *(r++) = *(s--) - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + } + break; + case 3: + while (s < seg_end) + { + int pred = coefs[2] * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 2; + } + break; + case 4: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 3; + } + break; + case 5: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 4; + } + break; + case 6: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 5; + } + break; + case 7: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 6; + } + break; + case 8: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 7; + } + break; + case 9: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 8; + } + break; + case 10: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 9; + } + break; + case 11: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 10; + } + break; + case 12: + while (s < seg_end) + { + int* c = coefs + order - 1; + int pred = + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 11; + } + break; + default: + while (s < seg_end) + { + int pred = 0; + int* c = coefs + order - 1; + int* c11 = coefs + 11; + while (c > c11) + pred += *(c--) * *(s++); + pred += + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + *(c--) * *(s++) + *(c--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + int d = *(r++) = *s - (pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= order - 1; + } + break; + } + return sum; + } + + public static unsafe void + encode_residual(int* res, int* smp, int n, int order, + int* coefs, int shift, ulong* sums, int pmax) + { + for (int i = 0; i < order; i++) + res[i] = smp[i]; + + int* s = smp; + int* s_end = smp + n - order; + int* seg_end = s + (n >> pmax) - order; + int* r = res + order; + while (s < s_end) + { + *(sums++) = encode_residual_partition(s, r, seg_end, coefs, shift, order); + r += seg_end - s; + s = seg_end; + seg_end += n >> pmax; + } + } + + private static unsafe ulong + encode_residual_long_partition(int* s, int* r, int* seg_end, int* coefs, int shift, int order) + { + ulong sum = 0ul; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + while (s < seg_end) + { + long pred = c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + } + break; + case 2: + while (s < seg_end) + { + long pred = c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *(s--) - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + } + break; + case 3: + while (s < seg_end) + { + long pred = coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 2; + } + break; + case 4: + while (s < seg_end) + { + long pred = coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 3; + } + break; + case 5: + while (s < seg_end) + { + long pred = coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 4; + } + break; + case 6: + while (s < seg_end) + { + long pred = coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 5; + } + break; + case 7: + while (s < seg_end) + { + long pred = coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 6; + } + break; + case 8: + while (s < seg_end) + { + long pred = coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= 7; + } + break; + default: + while (s < seg_end) + { + long pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * (long)*(s++); + pred += coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + int d = *(r++) = *s - (int)(pred >> shift); + sum += (uint)((d << 1) ^ (d >> 31)); + s -= order - 1; + } + break; + } + return sum; + } + + public static unsafe void + encode_residual_long(int* res, int* smp, int n, int order, + int* coefs, int shift, ulong* sums, int pmax) + { + for (int i = 0; i < order; i++) + res[i] = smp[i]; + + int* s = smp; + int* s_end = smp + n - order; + int* seg_end = s + (n >> pmax) - order; + int* r = res + order; + while (s < s_end) + { + *(sums++) = encode_residual_long_partition(s, r, seg_end, coefs, shift, order); + r += seg_end - s; + s = seg_end; + seg_end += n >> pmax; + } + } + + public static unsafe void + decode_residual(int* res, int* smp, int n, int order, + int* coefs, int shift) + { + for (int i = 0; i < order; i++) + smp[i] = res[i]; + + int* s = smp; + int* r = res + order; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + for (int i = n - order; i > 0; i--) + { + int pred = c0 * *(s++); + *s = *(r++) + (pred >> shift); + } + break; + case 2: + for (int i = n - order; i > 0; i--) + { + int pred = c1 * *(s++) + c0 * *(s++); + *(s--) = *(r++) + (pred >> shift); + } + break; + case 3: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 2; + } + break; + case 4: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 3; + } + break; + case 5: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 4; + } + break; + case 6: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 5; + } + break; + case 7: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 6; + } + break; + case 8: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 7; + } + break; + case 9: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 8; + } + break; + case 10: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 9; + } + break; + case 11: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 10; + } + break; + case 12: + for (int i = n - order; i > 0; i--) + { + int* co = coefs + order - 1; + int pred = + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + *(co--) * *(s++) + *(co--) * *(s++) + + c1 * *(s++) + c0 * *(s++); + *s = *(r++) + (pred >> shift); + s -= 11; + } + break; + default: + for (int i = order; i < n; i++) + { + s = smp + i - order; + int pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * *(s++); + pred += coefs[7] * *(s++); + pred += coefs[6] * *(s++); + pred += coefs[5] * *(s++); + pred += coefs[4] * *(s++); + pred += coefs[3] * *(s++); + pred += coefs[2] * *(s++); + pred += c1 * *(s++); + pred += c0 * *(s++); + *s = *(r++) + (pred >> shift); + } + break; + } + } + public static unsafe void + decode_residual_long(int* res, int* smp, int n, int order, + int* coefs, int shift) + { + for (int i = 0; i < order; i++) + smp[i] = res[i]; + + int* s = smp; + int* r = res + order; + int c0 = coefs[0]; + int c1 = coefs[1]; + switch (order) + { + case 1: + for (int i = n - order; i > 0; i--) + { + long pred = c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + } + break; + case 2: + for (int i = n - order; i > 0; i--) + { + long pred = c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *(s--) = *(r++) + (int)(pred >> shift); + } + break; + case 3: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 2; + } + break; + case 4: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 3; + } + break; + case 5: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 4; + } + break; + case 6: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 5; + } + break; + case 7: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 6; + } + break; + case 8: + for (int i = n - order; i > 0; i--) + { + long pred = coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + s -= 7; + } + break; + default: + for (int i = order; i < n; i++) + { + s = smp + i - order; + long pred = 0; + int* co = coefs + order - 1; + int* c7 = coefs + 7; + while (co > c7) + pred += *(co--) * (long)*(s++); + pred += coefs[7] * (long)*(s++); + pred += coefs[6] * (long)*(s++); + pred += coefs[5] * (long)*(s++); + pred += coefs[4] * (long)*(s++); + pred += coefs[3] * (long)*(s++); + pred += coefs[2] * (long)*(s++); + pred += c1 * (long)*(s++); + pred += c0 * (long)*(s++); + *s = *(r++) + (int)(pred >> shift); + } + break; + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/License.txt b/Aaru.Compression/cuetools.net/CUETools.Codecs/License.txt new file mode 100644 index 000000000..389f55a8b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/License.txt @@ -0,0 +1,16 @@ +CUETools.Codecs: common audio encoder/decoder routines +Copyright (c) 2009-2021 Gregory S. Chudov + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA \ No newline at end of file diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/LpcContext.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/LpcContext.cs new file mode 100644 index 000000000..295a8c640 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/LpcContext.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; + +namespace CUETools.Codecs +{ + unsafe public class LpcSubframeInfo + { + public LpcSubframeInfo() + { + autocorr_section_values = new double[lpc.MAX_LPC_SECTIONS, lpc.MAX_LPC_ORDER + 1]; + autocorr_section_orders = new int[lpc.MAX_LPC_SECTIONS]; + } + + // public LpcContext[] lpc_ctx; + public double[,] autocorr_section_values; + public int[] autocorr_section_orders; + //public int obits; + + public void Reset() + { + for (int sec = 0; sec < autocorr_section_orders.Length; sec++) + autocorr_section_orders[sec] = 0; + } + } + + unsafe public struct LpcWindowSection + { + public enum SectionType + { + Zero, + One, + OneLarge, + Data, + OneGlue, + Glue + }; + public int m_start; + public int m_end; + public SectionType m_type; + public int m_id; + public LpcWindowSection(int end) + { + m_id = -1; + m_start = 0; + m_end = end; + m_type = SectionType.Data; + } + public void setData(int start, int end) + { + m_id = -1; + m_start = start; + m_end = end; + m_type = SectionType.Data; + } + public void setOne(int start, int end) + { + m_id = -1; + m_start = start; + m_end = end; + m_type = SectionType.One; + } + public void setGlue(int start) + { + m_id = -1; + m_start = start; + m_end = start; + m_type = SectionType.Glue; + } + public void setZero(int start, int end) + { + m_id = -1; + m_start = start; + m_end = end; + m_type = SectionType.Zero; + } + + unsafe public void compute_autocorr(/*const*/ int* data, float* window, int min_order, int order, int blocksize, double* autoc) + { + if (m_type == SectionType.OneLarge) + lpc.compute_autocorr_windowless_large(data + m_start, m_end - m_start, min_order, order, autoc); + else if (m_type == SectionType.One) + lpc.compute_autocorr_windowless(data + m_start, m_end - m_start, min_order, order, autoc); + else if (m_type == SectionType.Data) + lpc.compute_autocorr(data + m_start, window + m_start, m_end - m_start, min_order, order, autoc); + else if (m_type == SectionType.Glue) + lpc.compute_autocorr_glue(data, window, m_start, m_end, min_order, order, autoc); + else if (m_type == SectionType.OneGlue) + lpc.compute_autocorr_glue(data + m_start, min_order, order, autoc); + } + + unsafe public static void Detect(int _windowcount, float* window_segment, int stride, int sz, int bps, LpcWindowSection* sections) + { + int section_id = 0; + var boundaries = new List(); + var types = new LpcWindowSection.SectionType[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; + var alias = new int[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; + var alias_set = new int[_windowcount, lpc.MAX_LPC_SECTIONS * 2]; + for (int x = 0; x < sz; x++) + { + for (int i = 0; i < _windowcount; i++) + { + int a = alias[i, boundaries.Count]; + float w = window_segment[i * stride + x]; + float wa = window_segment[a * stride + x]; + if (wa != w) + { + for (int i1 = i; i1 < _windowcount; i1++) + if (alias[i1, boundaries.Count] == a + && w == window_segment[i1 * stride + x]) + alias[i1, boundaries.Count] = i; + } + if (boundaries.Count >= lpc.MAX_LPC_SECTIONS * 2) throw new IndexOutOfRangeException(); + types[i, boundaries.Count] = + boundaries.Count >= lpc.MAX_LPC_SECTIONS * 2 - 2 ? + LpcWindowSection.SectionType.Data : w == 0.0 ? + LpcWindowSection.SectionType.Zero : w != 1.0 ? + LpcWindowSection.SectionType.Data : bps * 2 + BitReader.log2i(sz) >= 61 ? + LpcWindowSection.SectionType.OneLarge : + LpcWindowSection.SectionType.One ; + } + bool isBoundary = false; + for (int i = 0; i < _windowcount; i++) + { + isBoundary |= boundaries.Count == 0 || + types[i, boundaries.Count - 1] != types[i, boundaries.Count]; + } + if (isBoundary) + { + for (int i = 0; i < _windowcount; i++) + for (int i1 = 0; i1 < _windowcount; i1++) + if (i != i1 && alias[i, boundaries.Count] == alias[i1, boundaries.Count]) + alias_set[i, boundaries.Count] |= 1 << i1; + boundaries.Add(x); + } + } + boundaries.Add(sz); + var secs = new int[_windowcount]; + // Reconstruct segments list. + for (int j = 0; j < boundaries.Count - 1; j++) + { + for (int i = 0; i < _windowcount; i++) + { + LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; + // leave room for glue + if (secs[i] >= lpc.MAX_LPC_SECTIONS - 1) + { + throw new IndexOutOfRangeException(); + //window_sections[secs[i] - 1].m_type = LpcWindowSection.SectionType.Data; + //window_sections[secs[i] - 1].m_end = boundaries[j + 1]; + //continue; + } + window_sections[secs[i]].setData(boundaries[j], boundaries[j + 1]); + window_sections[secs[i]++].m_type = types[i, j]; + } + for (int i = 0; i < _windowcount; i++) + { + LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; + int sec = secs[i] - 1; + if (sec > 0 + && j > 0 && (alias_set[i, j] == alias_set[i, j - 1] || window_sections[sec].m_type == SectionType.Zero) + && window_sections[sec].m_start == boundaries[j] + && window_sections[sec].m_end == boundaries[j + 1] + && window_sections[sec - 1].m_end == boundaries[j] + && window_sections[sec - 1].m_type == window_sections[sec].m_type) + { + window_sections[sec - 1].m_end = window_sections[sec].m_end; + secs[i]--; + continue; + } + if (section_id >= lpc.MAX_LPC_SECTIONS) throw new IndexOutOfRangeException(); + if (alias_set[i, j] != 0 + && types[i, j] != SectionType.Zero + && section_id < lpc.MAX_LPC_SECTIONS) + { + for (int i1 = i; i1 < _windowcount; i1++) + if (alias[i1, j] == i && secs[i1] > 0) + sections[i1 * lpc.MAX_LPC_SECTIONS + secs[i1] - 1].m_id = section_id; + section_id++; + } + // TODO: section_id for glue? nontrivial, must be sure next sections are the same size + if (sec > 0 + && (window_sections[sec].m_type == SectionType.One || window_sections[sec].m_type == SectionType.OneLarge) + && window_sections[sec].m_end - window_sections[sec].m_start >= lpc.MAX_LPC_ORDER + && (window_sections[sec - 1].m_type == SectionType.One || window_sections[sec - 1].m_type == SectionType.OneLarge) + && window_sections[sec - 1].m_end - window_sections[sec - 1].m_start >= lpc.MAX_LPC_ORDER) + { + window_sections[sec + 1] = window_sections[sec]; + window_sections[sec].m_end = window_sections[sec].m_start; + window_sections[sec].m_type = SectionType.OneGlue; + window_sections[sec].m_id = -1; + secs[i]++; + continue; + } + if (sec > 0 + && window_sections[sec].m_type != SectionType.Zero + && window_sections[sec - 1].m_type != SectionType.Zero) + { + window_sections[sec + 1] = window_sections[sec]; + window_sections[sec].m_end = window_sections[sec].m_start; + window_sections[sec].m_type = SectionType.Glue; + window_sections[sec].m_id = -1; + secs[i]++; + continue; + } + } + } + for (int i = 0; i < _windowcount; i++) + { + for (int s = 0; s < secs[i]; s++) + { + LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; + if (window_sections[s].m_type == SectionType.Glue + || window_sections[s].m_type == SectionType.OneGlue) + { + window_sections[s].m_end = window_sections[s + 1].m_end; + } + } + while (secs[i] < lpc.MAX_LPC_SECTIONS) + { + LpcWindowSection* window_sections = sections + i * lpc.MAX_LPC_SECTIONS; + window_sections[secs[i]++].setZero(sz, sz); + } + } + } + } + + /// + /// Context for LPC coefficients calculation and order estimation + /// + unsafe public class LpcContext + { + public LpcContext() + { + coefs = new int[lpc.MAX_LPC_ORDER]; + reflection_coeffs = new double[lpc.MAX_LPC_ORDER]; + prediction_error = new double[lpc.MAX_LPC_ORDER]; + autocorr_values = new double[lpc.MAX_LPC_ORDER + 1]; + best_orders = new int[lpc.MAX_LPC_ORDER]; + done_lpcs = new uint[lpc.MAX_LPC_PRECISIONS]; + } + + /// + /// Reset to initial (blank) state + /// + public void Reset() + { + autocorr_order = 0; + for (int iPrecision = 0; iPrecision < lpc.MAX_LPC_PRECISIONS; iPrecision++) + done_lpcs[iPrecision] = 0; + } + + /// + /// Calculate autocorrelation data and reflection coefficients. + /// Can be used to incrementaly compute coefficients for higher orders, + /// because it caches them. + /// + /// Maximum order + /// Samples pointer + /// Block size + /// Window function + public void GetReflection(LpcSubframeInfo subframe, int order, int blocksize, int* samples, float* window, LpcWindowSection* sections) + { + if (autocorr_order > order) + return; + fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) + { + for (int i = autocorr_order; i <= order; i++) autoc[i] = 0; + for (int section = 0; section < lpc.MAX_LPC_SECTIONS; section++) + { + if (sections[section].m_type == LpcWindowSection.SectionType.Zero) + { + continue; + } + if (sections[section].m_id >= 0) + { + if (subframe.autocorr_section_orders[sections[section].m_id] <= order) + { + fixed (double* autocsec = &subframe.autocorr_section_values[sections[section].m_id, 0]) + { + int min_order = subframe.autocorr_section_orders[sections[section].m_id]; + for (int i = min_order; i <= order; i++) autocsec[i] = 0; + sections[section].compute_autocorr(samples, window, min_order, order, blocksize, autocsec); + } + subframe.autocorr_section_orders[sections[section].m_id] = order + 1; + } + for (int i = autocorr_order; i <= order; i++) + autoc[i] += subframe.autocorr_section_values[sections[section].m_id, i]; + } + else + { + sections[section].compute_autocorr(samples, window, autocorr_order, order, blocksize, autoc); + } + } + lpc.compute_schur_reflection(autoc, (uint)order, reff, err); + autocorr_order = order + 1; + } + } +#if XXX + public void GetReflection1(int order, int* samples, int blocksize, float* window) + { + if (autocorr_order > order) + return; + fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) + { + lpc.compute_autocorr(samples, blocksize, 0, order + 1, autoc, window); + for (int i = 1; i <= order; i++) + autoc[i] = autoc[i + 1]; + lpc.compute_schur_reflection(autoc, (uint)order, reff, err); + autocorr_order = order + 1; + } + } + + public void ComputeReflection(int order, float* autocorr) + { + fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) + { + for (int i = 0; i <= order; i++) + autoc[i] = autocorr[i]; + lpc.compute_schur_reflection(autoc, (uint)order, reff, err); + autocorr_order = order + 1; + } + } + + public void ComputeReflection(int order, double* autocorr) + { + fixed (double* reff = reflection_coeffs, autoc = autocorr_values, err = prediction_error) + { + for (int i = 0; i <= order; i++) + autoc[i] = autocorr[i]; + lpc.compute_schur_reflection(autoc, (uint)order, reff, err); + autocorr_order = order + 1; + } + } +#endif + public double Akaike(int blocksize, int order, double alpha, double beta) + { + //return (blocksize - order) * (Math.Log(prediction_error[order - 1]) - Math.Log(1.0)) + Math.Log(blocksize) * order * (alpha + beta * order); + //return blocksize * (Math.Log(prediction_error[order - 1]) - Math.Log(autocorr_values[0]) / 2) + Math.Log(blocksize) * order * (alpha + beta * order); + return blocksize * (Math.Log(prediction_error[order - 1])) + Math.Log(blocksize) * order * (alpha + beta * order); + } + + /// + /// Sorts orders based on Akaike's criteria + /// + /// Frame size + public void SortOrdersAkaike(int blocksize, int count, int min_order, int max_order, double alpha, double beta) + { + for (int i = min_order; i <= max_order; i++) + best_orders[i - min_order] = i; + int lim = max_order - min_order + 1; + for (int i = 0; i < lim && i < count; i++) + { + for (int j = i + 1; j < lim; j++) + { + if (Akaike(blocksize, best_orders[j], alpha, beta) < Akaike(blocksize, best_orders[i], alpha, beta)) + { + int tmp = best_orders[j]; + best_orders[j] = best_orders[i]; + best_orders[i] = tmp; + } + } + } + } + + /// + /// Produces LPC coefficients from autocorrelation data. + /// + /// LPC coefficients buffer (for all orders) + public void ComputeLPC(float* lpcs) + { + fixed (double* reff = reflection_coeffs) + lpc.compute_lpc_coefs((uint)autocorr_order - 1, reff, lpcs); + } + + public double[] autocorr_values; + double[] reflection_coeffs; + public double[] prediction_error; + public int[] best_orders; + public int[] coefs; + int autocorr_order; + public int shift; + + public double[] Reflection + { + get + { + return reflection_coeffs; + } + } + + public uint[] done_lpcs; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioDecoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioDecoder.cs new file mode 100644 index 000000000..651db6a22 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioDecoder.cs @@ -0,0 +1,65 @@ +using System; + +namespace CUETools.Codecs.NULL +{ + public class AudioDecoder : IAudioSource + { + private long _sampleOffset, _sampleCount; + private AudioPCMConfig pcm; + private int _sampleVal; + + public IAudioDecoderSettings Settings => null; + + public TimeSpan Duration => TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length + { + get { return _sampleCount; } + } + + public long Remaining + { + get { return _sampleCount - _sampleOffset; } + } + + public long Position + { + get { return _sampleOffset; } + set { _sampleOffset = value; } + } + + public AudioPCMConfig PCM { get { return pcm; } } + + public string Path { get { return null; } } + + public AudioDecoder(AudioPCMConfig pcm, long sampleCount, int sampleVal) + { + this._sampleVal = sampleVal; + this._sampleOffset = 0; + this._sampleCount = sampleCount; + this.pcm = pcm; + } + + public AudioDecoder(long sampleCount) + : this(AudioPCMConfig.RedBook, sampleCount, 0) + { + } + + public int Read(AudioBuffer buff, int maxLength) + { + buff.Prepare(this, maxLength); + + int[,] samples = buff.Samples; + for (int i = 0; i < buff.Length; i++) + for (int j = 0; j < PCM.ChannelCount; j++) + samples[i, j] = _sampleVal; + + _sampleOffset += buff.Length; + return buff.Length; + } + + public void Close() + { + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioEncoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioEncoder.cs new file mode 100644 index 000000000..fbeb36749 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/NULL/AudioEncoder.cs @@ -0,0 +1,35 @@ +using System; + +namespace CUETools.Codecs.NULL +{ + public class AudioEncoder : IAudioDest + { + IAudioEncoderSettings m_settings; + + public AudioEncoder(string path, IAudioEncoderSettings settings) + { + m_settings = settings; + } + + public void Close() + { + } + + public void Delete() + { + } + + public long FinalSampleCount + { + set { } + } + + public IAudioEncoderSettings Settings => m_settings; + + public void Write(AudioBuffer buff) + { + } + + public string Path => null; + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/NullStream.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/NullStream.cs new file mode 100644 index 000000000..a19b70a3d --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/NullStream.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; + +namespace CUETools.Codecs +{ + public class NullStream : Stream + { + public override bool CanRead + { + get { return false; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return true; } + } + + public override long Length + { + get { throw new NotSupportedException(); } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public NullStream() + { + } + + public override void Close() + { + } + + public override void Flush() + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] array, int offset, int count) + { + throw new NotSupportedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] array, int offset, int count) + { + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/PlaybackState.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/PlaybackState.cs new file mode 100644 index 000000000..e4a49d13b --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/PlaybackState.cs @@ -0,0 +1,21 @@ +namespace CUETools.Codecs +{ + /// + /// Playback State + /// + public enum PlaybackState + { + /// + /// Stopped + /// + Stopped, + /// + /// Playing + /// + Playing, + /// + /// Paused + /// + Paused + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/SRDescriptionAttribute.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/SRDescriptionAttribute.cs new file mode 100644 index 000000000..6c3e2fddd --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/SRDescriptionAttribute.cs @@ -0,0 +1,56 @@ +using System; +using System.ComponentModel; + +namespace CUETools.Codecs +{ + /// + /// Localized description attribute + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public class SRDescriptionAttribute : DescriptionAttribute + { + /// + /// Store a flag indicating whether this has been localized + /// + private bool localized; + + /// + /// Resource manager to use; + /// + private Type SR; + + /// + /// Construct the description attribute + /// + /// + public SRDescriptionAttribute(Type SR, string text) + : base(text) + { + this.localized = false; + this.SR = SR; + } + + /// + /// Override the return of the description text to localize the text + /// + public override string Description + { + get + { + if (!localized) + { + localized = true; + this.DescriptionValue = SR.InvokeMember( + this.DescriptionValue, + System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Static | + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic, + null, + null, + new object[] { }) as string; + } + + return base.Description; + } + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioDecoderSettingsViewModel.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioDecoderSettingsViewModel.cs new file mode 100644 index 000000000..a58d23e7e --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioDecoderSettingsViewModel.cs @@ -0,0 +1,97 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel; + +namespace CUETools.Codecs +{ + [JsonObject(MemberSerialization.OptIn)] + public class AudioDecoderSettingsViewModel : INotifyPropertyChanged + { + [JsonProperty] + public IAudioDecoderSettings Settings = null; + + public event PropertyChangedEventHandler PropertyChanged; + + [JsonConstructor] + private AudioDecoderSettingsViewModel() + { + } + + public AudioDecoderSettingsViewModel(IAudioDecoderSettings settings) + { + this.Settings = settings; + } + + public override string ToString() + { + return Name; + } + + public string FullName => Name + " [" + Extension + "]"; + + public string Path + { + get + { + if (Settings is CommandLine.DecoderSettings) + return (Settings as CommandLine.DecoderSettings).Path; + return ""; + } + set + { + if (Settings is CommandLine.DecoderSettings) + (Settings as CommandLine.DecoderSettings).Path = value; + else throw new InvalidOperationException(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Path")); + } + } + public string Parameters + { + get + { + if (Settings is CommandLine.DecoderSettings) + return (Settings as CommandLine.DecoderSettings).Parameters; + return ""; + } + set + { + if (Settings is CommandLine.DecoderSettings) + (Settings as CommandLine.DecoderSettings).Parameters = value; + else throw new InvalidOperationException(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Parameters")); + } + } + + public string Name + { + get => Settings.Name; + set + { + if (Settings is CommandLine.DecoderSettings) + (Settings as CommandLine.DecoderSettings).Name = value; + else throw new InvalidOperationException(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); + } + } + + public string Extension + { + get => Settings.Extension; + set + { + if (Settings is CommandLine.DecoderSettings) + (Settings as CommandLine.DecoderSettings).Extension = value; + else throw new InvalidOperationException(); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Extension")); + } + } + + public string DotExtension => "." + Extension; + + public bool CanBeDeleted => Settings is CommandLine.DecoderSettings; + + public bool IsValid => + (Settings != null) + && (Settings is CommandLine.DecoderSettings ? (Settings as CommandLine.DecoderSettings).Path != "" : true); + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioEncoderSettingsViewModel.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioEncoderSettingsViewModel.cs new file mode 100644 index 000000000..a9e0f1062 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/AudioEncoderSettingsViewModel.cs @@ -0,0 +1,137 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel; + +namespace CUETools.Codecs +{ + [JsonObject(MemberSerialization.OptIn)] + public class AudioEncoderSettingsViewModel : INotifyPropertyChanged + { + [JsonProperty] + public IAudioEncoderSettings Settings = null; + + public event PropertyChangedEventHandler PropertyChanged; + + [JsonConstructor] + private AudioEncoderSettingsViewModel() + { + } + + public AudioEncoderSettingsViewModel(IAudioEncoderSettings settings) + { + this.Settings = settings; + } + + public override string ToString() + { + return Name; + } + + public string FullName => Name + " [" + Extension + "]"; + + public string Path + { + get + { + if (Settings is CommandLine.EncoderSettings) + return (Settings as CommandLine.EncoderSettings).Path; + return ""; + } + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.Path = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Path")); + } + } + + public string Parameters + { + get + { + if (Settings is CommandLine.EncoderSettings) + return (Settings as CommandLine.EncoderSettings).Parameters; + return ""; + } + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.Parameters = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Parameters")); + } + } + + public bool Lossless + { + get => Settings.Lossless; + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.Lossless = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Lossless")); + } + } + + + public string Name + { + get => Settings.Name; + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.Name = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name")); + } + } + + public string Extension + { + get => Settings.Extension; + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.Extension = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Extension")); + } + } + + public string DotExtension => "." + Extension; + + public string SupportedModes + { + get => Settings.SupportedModes; + set + { + var settings = this.Settings as CommandLine.EncoderSettings; + if (settings == null) throw new InvalidOperationException(); + settings.SupportedModes = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SupportedModes")); + } + } + + public int EncoderModeIndex + { + get + { + string[] modes = this.SupportedModes.Split(' '); + if (modes == null || modes.Length < 2) + return -1; + for (int i = 0; i < modes.Length; i++) + if (modes[i] == this.Settings.EncoderMode) + return i; + return -1; + } + } + + public bool CanBeDeleted => Settings is CommandLine.EncoderSettings; + + public bool IsValid => + (Settings != null) + && (Settings is CommandLine.EncoderSettings ? (Settings as CommandLine.EncoderSettings).Path != "" : true); + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/DecoderListViewModel.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/DecoderListViewModel.cs new file mode 100644 index 000000000..0a790cf18 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/DecoderListViewModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace CUETools.Codecs +{ + public class DecoderListViewModel : BindingList + { + private List model; + + public DecoderListViewModel(List model) + : base() + { + this.model = model; + model.ForEach(item => Add(new AudioDecoderSettingsViewModel(item))); + AddingNew += OnAddingNew; + } + + private void OnAddingNew(object sender, AddingNewEventArgs e) + { + var item = new CommandLine.DecoderSettings("new", "wav", "", ""); + model.Add(item); + e.NewObject = new AudioDecoderSettingsViewModel(item); + } + + public bool TryGetValue(string extension, string name, out AudioDecoderSettingsViewModel result) + { + foreach (AudioDecoderSettingsViewModel udc in this) + { + if (udc.Settings.Extension == extension && udc.Settings.Name == name) + { + result = udc; + return true; + } + } + result = null; + return false; + } + + public AudioDecoderSettingsViewModel GetDefault(string extension) + { + AudioDecoderSettingsViewModel result = null; + foreach (AudioDecoderSettingsViewModel udc in this) + { + if (udc.Settings.Extension == extension && (result == null || result.Settings.Priority < udc.Settings.Priority)) + { + result = udc; + } + } + return result; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/EncoderListViewModel.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/EncoderListViewModel.cs new file mode 100644 index 000000000..1c5ba2c20 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/ViewModel/EncoderListViewModel.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace CUETools.Codecs +{ + public class EncoderListViewModel : BindingList + { + private List model; + + public EncoderListViewModel(List model) + : base() + { + this.model = model; + model.ForEach(item => Add(new AudioEncoderSettingsViewModel(item))); + AddingNew += OnAddingNew; + } + + private void OnAddingNew(object sender, AddingNewEventArgs e) + { + var item = new CommandLine.EncoderSettings("new", "wav", true, "", "", "", ""); + model.Add(item); + e.NewObject = new AudioEncoderSettingsViewModel(item); + } + + public bool TryGetValue(string extension, bool lossless, string name, out AudioEncoderSettingsViewModel result) + { + //result = this.Where(udc => udc.settings.Extension == extension && udc.settings.Lossless == lossless && udc.settings.Name == name).First(); + foreach (AudioEncoderSettingsViewModel udc in this) + { + if (udc.Settings.Extension == extension && udc.Settings.Lossless == lossless && udc.Settings.Name == name) + { + result = udc; + return true; + } + } + result = null; + return false; + } + + public AudioEncoderSettingsViewModel GetDefault(string extension, bool lossless) + { + AudioEncoderSettingsViewModel result = null; + foreach (AudioEncoderSettingsViewModel udc in this) + { + if (udc.Settings.Extension == extension && udc.Settings.Lossless == lossless && (result == null || result.Settings.Priority < udc.Settings.Priority)) + { + result = udc; + } + } + return result; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioDecoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioDecoder.cs new file mode 100644 index 000000000..3cd32d0a3 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioDecoder.cs @@ -0,0 +1,271 @@ +using System; +using System.IO; + +namespace CUETools.Codecs.WAV +{ + public class AudioDecoder : IAudioSource + { + Stream _IO; + BinaryReader _br; + long _dataOffset, _samplePos, _sampleLen; + private AudioPCMConfig pcm; + long _dataLen; + bool _largeFile; + string _path; + + private DecoderSettings m_settings; + public IAudioDecoderSettings Settings => m_settings; + + public long Position + { + get + { + return _samplePos; + } + set + { + long seekPos; + + if (_samplePos == value) + { + return; + } + + var oldSamplePos = _samplePos; + if (_sampleLen >= 0 && value > _sampleLen) + _samplePos = _sampleLen; + else + _samplePos = value; + + if (_IO.CanSeek || _samplePos < oldSamplePos) + { + seekPos = _dataOffset + _samplePos * PCM.BlockAlign; + _IO.Seek(seekPos, SeekOrigin.Begin); + } + else + { + int offs = (int)(_samplePos - oldSamplePos) * PCM.BlockAlign; + while (offs > 0) + { + int chunk = Math.Min(offs, 16536); + _br.ReadBytes(chunk); + offs -= chunk; + } + } + } + } + + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length + { + get + { + return _sampleLen; + } + } + + public long Remaining + { + get + { + return _sampleLen - _samplePos; + } + } + + public AudioPCMConfig PCM { get { return pcm; } } + + public string Path { get { return _path; } } + + public AudioDecoder(DecoderSettings settings, string path, Stream IO = null) + { + m_settings = settings; + _path = path; + _IO = IO ?? new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan); + _br = new BinaryReader(_IO); + + ParseHeaders(); + + if (_dataLen < 0 || m_settings.IgnoreChunkSizes) + _sampleLen = -1; + else + _sampleLen = _dataLen / pcm.BlockAlign; + } + + public AudioDecoder(DecoderSettings settings, string path, Stream IO, AudioPCMConfig _pcm) + { + m_settings = settings; + _path = path; + _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan); + _br = new BinaryReader(_IO); + + _largeFile = false; + _dataOffset = 0; + _samplePos = 0; + pcm = _pcm; + _dataLen = _IO.CanSeek ? _IO.Length : -1; + if (_dataLen < 0) + _sampleLen = -1; + else + { + _sampleLen = _dataLen / pcm.BlockAlign; + if ((_dataLen % pcm.BlockAlign) != 0) + throw new Exception("odd file size"); + } + } + + public static AudioBuffer ReadAllSamples(DecoderSettings settings, string path, Stream IO = null) + { + AudioDecoder reader = new AudioDecoder(settings, path, IO); + AudioBuffer buff = new AudioBuffer(reader, (int)reader.Length); + reader.Read(buff, -1); + if (reader.Remaining != 0) + throw new Exception("couldn't read the whole file"); + reader.Close(); + return buff; + } + + public void Close() + { + if (_br != null) + { + _br.Close(); + _br = null; + } + _IO = null; + } + + private void ParseHeaders() + { + const long maxFileSize = 0x7FFFFFFEL; + const uint fccRIFF = 0x46464952; + const uint fccWAVE = 0x45564157; + const uint fccFormat = 0x20746D66; + const uint fccData = 0x61746164; + + uint lenRIFF; + bool foundFormat, foundData; + + if (_br.ReadUInt32() != fccRIFF) + { + throw new Exception("Not a valid RIFF file."); + } + + lenRIFF = _br.ReadUInt32(); + + if (_br.ReadUInt32() != fccWAVE) + { + throw new Exception("Not a valid WAVE file."); + } + + _largeFile = false; + foundFormat = false; + foundData = false; + long pos = 12; + do + { + uint ckID, ckSize, ckSizePadded; + long ckEnd; + + ckID = _br.ReadUInt32(); + ckSize = _br.ReadUInt32(); + ckSizePadded = (ckSize + 1U) & ~1U; + pos += 8; + ckEnd = pos + (long)ckSizePadded; + + if (ckID == fccFormat) + { + foundFormat = true; + + uint fmtTag = _br.ReadUInt16(); + int _channelCount = _br.ReadInt16(); + int _sampleRate = _br.ReadInt32(); + _br.ReadInt32(); // bytes per second + int _blockAlign = _br.ReadInt16(); + int _bitsPerSample = _br.ReadInt16(); + int _channelMask = 0; + pos += 16; + + if (fmtTag == 0xFFFEU && ckSize >= 34) // WAVE_FORMAT_EXTENSIBLE + { + _br.ReadInt16(); // CbSize + _br.ReadInt16(); // ValidBitsPerSample + _channelMask = _br.ReadInt32(); + fmtTag = _br.ReadUInt16(); + pos += 10; + } + + if (fmtTag != 1) // WAVE_FORMAT_PCM + throw new Exception("WAVE format tag not WAVE_FORMAT_PCM."); + + pcm = new AudioPCMConfig(_bitsPerSample, _channelCount, _sampleRate, (AudioPCMConfig.SpeakerConfig)_channelMask); + if (pcm.BlockAlign != _blockAlign) + throw new Exception("WAVE has strange BlockAlign"); + } + else if (ckID == fccData) + { + foundData = true; + + _dataOffset = pos; + if (!_IO.CanSeek || _IO.Length <= maxFileSize) + { + if (ckSize == 0 || ckSize >= 0x7fffffff) + _dataLen = -1; + else + _dataLen = (long)ckSize; + } + else + { + _largeFile = true; + _dataLen = _IO.Length - pos; + } + } + + if ((foundFormat & foundData) || _largeFile) + break; + if (_IO.CanSeek) + _IO.Seek(ckEnd, SeekOrigin.Begin); + else + _br.ReadBytes((int)(ckEnd - pos)); + pos = ckEnd; + } while (true); + + if ((foundFormat & foundData) == false || pcm == null) + throw new Exception("Format or data chunk not found."); + if (pcm.ChannelCount <= 0) + throw new Exception("Channel count is invalid."); + if (pcm.SampleRate <= 0) + throw new Exception("Sample rate is invalid."); + if ((pcm.BitsPerSample <= 0) || (pcm.BitsPerSample > 32)) + throw new Exception("Bits per sample is invalid."); + if (pos != _dataOffset) + Position = 0; + } + + public int Read(AudioBuffer buff, int maxLength) + { + buff.Prepare(this, maxLength); + + byte[] bytes = buff.Bytes; + int byteCount = (int)buff.ByteLength; + int pos = 0; + + while (pos < byteCount) + { + int len = _IO.Read(bytes, pos, byteCount - pos); + if (len <= 0) + { + if ((pos % PCM.BlockAlign) != 0 || _sampleLen >= 0) + throw new Exception("Incomplete file read."); + buff.Length = pos / PCM.BlockAlign; + _samplePos += buff.Length; + _sampleLen = _samplePos; + return buff.Length; + } + pos += len; + } + _samplePos += buff.Length; + return buff.Length; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioEncoder.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioEncoder.cs new file mode 100644 index 000000000..3f32eb0a9 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/AudioEncoder.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace CUETools.Codecs.WAV +{ + public class AudioEncoder : IAudioDest + { + private Stream _IO; + private BinaryWriter _bw; + private long _sampleLen; + private string _path; + private long hdrLen = 0; + private bool _headersWritten = false; + private long _finalSampleCount = -1; + private List _chunks = null; + private List _chunkFCCs = null; + + public long Position + { + get + { + return _sampleLen; + } + } + + public long FinalSampleCount + { + set { _finalSampleCount = value; } + } + + private EncoderSettings m_settings; + public IAudioEncoderSettings Settings => m_settings; + + public string Path { get { return _path; } } + + public AudioEncoder(EncoderSettings settings, string path, Stream IO = null) + { + m_settings = settings; + _path = path; + _IO = IO ?? new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); + _bw = new BinaryWriter(_IO); + } + + public void WriteChunk(uint fcc, byte[] data) + { + if (_sampleLen > 0) + throw new Exception("data already written, no chunks allowed"); + if (_chunks == null) + { + _chunks = new List(); + _chunkFCCs = new List(); + } + _chunkFCCs.Add(fcc); + _chunks.Add(data); + hdrLen += 8 + data.Length + (data.Length & 1); + } + + private void WriteHeaders() + { + const uint fccRIFF = 0x46464952; + const uint fccWAVE = 0x45564157; + const uint fccFormat = 0x20746D66; + const uint fccData = 0x61746164; + + bool wavex = (Settings.PCM.BitsPerSample != 16 && Settings.PCM.BitsPerSample != 24) || Settings.PCM.ChannelMask != AudioPCMConfig.GetDefaultChannelMask(Settings.PCM.ChannelCount); + + hdrLen += 36 + (wavex ? 24 : 0) + 8; + + uint dataLen = (uint)(_finalSampleCount * Settings.PCM.BlockAlign); + uint dataLenPadded = dataLen + (dataLen & 1); + + _bw.Write(fccRIFF); + if (_finalSampleCount <= 0) + _bw.Write((uint)0xffffffff); + else + _bw.Write((uint)(dataLenPadded + hdrLen - 8)); + _bw.Write(fccWAVE); + _bw.Write(fccFormat); + if (wavex) + { + _bw.Write((uint)40); + _bw.Write((ushort)0xfffe); // WAVEX follows + } + else + { + _bw.Write((uint)16); + _bw.Write((ushort)1); // PCM + } + _bw.Write((ushort)Settings.PCM.ChannelCount); + _bw.Write((uint)Settings.PCM.SampleRate); + _bw.Write((uint)(Settings.PCM.SampleRate * Settings.PCM.BlockAlign)); + _bw.Write((ushort)Settings.PCM.BlockAlign); + _bw.Write((ushort)((Settings.PCM.BitsPerSample + 7) / 8 * 8)); + if (wavex) + { + _bw.Write((ushort)22); // length of WAVEX structure + _bw.Write((ushort)Settings.PCM.BitsPerSample); + _bw.Write((uint)Settings.PCM.ChannelMask); + _bw.Write((ushort)1); // PCM Guid + _bw.Write((ushort)0); + _bw.Write((ushort)0); + _bw.Write((ushort)0x10); + _bw.Write((byte)0x80); + _bw.Write((byte)0x00); + _bw.Write((byte)0x00); + _bw.Write((byte)0xaa); + _bw.Write((byte)0x00); + _bw.Write((byte)0x38); + _bw.Write((byte)0x9b); + _bw.Write((byte)0x71); + } + if (_chunks != null) + for (int i = 0; i < _chunks.Count; i++) + { + _bw.Write(_chunkFCCs[i]); + _bw.Write((uint)_chunks[i].Length); + _bw.Write(_chunks[i]); + if ((_chunks[i].Length & 1) != 0) + _bw.Write((byte)0); + } + + _bw.Write(fccData); + if (_finalSampleCount <= 0) + _bw.Write((uint)0xffffffff); + else + _bw.Write(dataLen); + + _headersWritten = true; + } + + public void Close() + { + if (_finalSampleCount <= 0 && _IO.CanSeek) + { + long dataLen = _sampleLen * Settings.PCM.BlockAlign; + long dataLenPadded = dataLen + (dataLen & 1); + if (dataLenPadded + hdrLen - 8 < 0xffffffff) + { + if ((dataLen & 1) == 1) + _bw.Write((byte)0); + + _bw.Seek(4, SeekOrigin.Begin); + _bw.Write((uint)(dataLenPadded + hdrLen - 8)); + + _bw.Seek((int)hdrLen - 4, SeekOrigin.Begin); + _bw.Write((uint)dataLen); + } + } + + _bw.Close(); + + _bw = null; + _IO = null; + + if (_finalSampleCount > 0 && _sampleLen != _finalSampleCount) + throw new Exception("Samples written differs from the expected sample count."); + } + + public void Delete() + { + _bw.Close(); + _bw = null; + _IO = null; + if (_path != "") + File.Delete(_path); + } + + public void Write(AudioBuffer buff) + { + if (buff.Length == 0) + return; + buff.Prepare(this); + if (!_headersWritten) + WriteHeaders(); + _IO.Write(buff.Bytes, 0, buff.ByteLength); + _sampleLen += buff.Length; + } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/DecoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/DecoderSettings.cs new file mode 100644 index 000000000..1163647bb --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/DecoderSettings.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel; + +namespace CUETools.Codecs.WAV +{ + [JsonObject(MemberSerialization.OptIn)] + public class DecoderSettings : IAudioDecoderSettings + { + #region IAudioDecoderSettings implementation + [Browsable(false)] + public string Extension => "wav"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type DecoderType => typeof(AudioDecoder); + + [Browsable(false)] + public int Priority => 2; + + public IAudioDecoderSettings Clone() + { + return MemberwiseClone() as IAudioDecoderSettings; + } + #endregion + + public DecoderSettings() + { + this.Init(); + } + + public bool IgnoreChunkSizes { get; set; } + } +} diff --git a/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/EncoderSettings.cs b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/EncoderSettings.cs new file mode 100644 index 000000000..2a55a0fb2 --- /dev/null +++ b/Aaru.Compression/cuetools.net/CUETools.Codecs/WAV/EncoderSettings.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; + +namespace CUETools.Codecs.WAV +{ + [JsonObject(MemberSerialization.OptIn)] + public class EncoderSettings : IAudioEncoderSettings + { + #region IAudioEncoderSettings implementation + [Browsable(false)] + public string Extension => "wav"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type EncoderType => typeof(WAV.AudioEncoder); + + [Browsable(false)] + public bool Lossless => true; + + [Browsable(false)] + public int Priority => 10; + + [Browsable(false)] + public string SupportedModes => ""; + + [Browsable(false)] + public string DefaultMode => ""; + + [Browsable(false)] + [DefaultValue("")] + public string EncoderMode { get; set; } + + [Browsable(false)] + public AudioPCMConfig PCM { get; set; } + + [Browsable(false)] + public int BlockSize { get; set; } + + [Browsable(false)] + [DefaultValue(4096)] + public int Padding { get; set; } + + public IAudioEncoderSettings Clone() + { + return MemberwiseClone() as IAudioEncoderSettings; + } + #endregion + + public EncoderSettings() + { + this.Init(); + } + + public EncoderSettings(AudioPCMConfig pcm) + { + this.Init(pcm); + } + } +}